@gethmy/mcp 2.6.0 → 2.7.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/dist/cli.js +140 -0
- package/dist/index.js +140 -0
- package/dist/lib/api-client.js +74 -0
- package/package.json +1 -1
- package/src/api-client.ts +88 -0
- package/src/prompt-builder.ts +53 -0
- package/src/server.ts +74 -0
package/dist/cli.js
CHANGED
|
@@ -69,6 +69,34 @@ function formatLabels(labels) {
|
|
|
69
69
|
return `
|
|
70
70
|
**Labels:** ${labels.map((l) => l.name).join(", ")}`;
|
|
71
71
|
}
|
|
72
|
+
function formatAttachments(attachments) {
|
|
73
|
+
if (!attachments || attachments.length === 0)
|
|
74
|
+
return "";
|
|
75
|
+
const lines = attachments.map((att) => {
|
|
76
|
+
const isImage = att.file_type.startsWith("image/");
|
|
77
|
+
const sizeKb = Math.max(1, Math.round(att.file_size / 1024));
|
|
78
|
+
const url = att.signed_url ?? "(signed URL unavailable)";
|
|
79
|
+
const kind = isImage ? "image" : att.file_type;
|
|
80
|
+
return ` - ${att.file_name} (${kind}, ${sizeKb} KB) — ${url}`;
|
|
81
|
+
});
|
|
82
|
+
return `
|
|
83
|
+
## Attachments
|
|
84
|
+
*Signed URLs expire in ~1 hour. Re-fetch via \`harmony_get_card_attachments\` if expired.*
|
|
85
|
+
${lines.join(`
|
|
86
|
+
`)}`;
|
|
87
|
+
}
|
|
88
|
+
function formatExternalLinks(refs) {
|
|
89
|
+
if (!refs || refs.length === 0)
|
|
90
|
+
return "";
|
|
91
|
+
const lines = refs.map((ref) => {
|
|
92
|
+
const label = ref.title?.trim() || ref.url;
|
|
93
|
+
return ` - ${label} — ${ref.url}`;
|
|
94
|
+
});
|
|
95
|
+
return `
|
|
96
|
+
## External References
|
|
97
|
+
${lines.join(`
|
|
98
|
+
`)}`;
|
|
99
|
+
}
|
|
72
100
|
function formatLinkedCards(links) {
|
|
73
101
|
if (!links || links.length === 0)
|
|
74
102
|
return "";
|
|
@@ -102,11 +130,15 @@ function generatePrompt(options) {
|
|
|
102
130
|
includePriority: true,
|
|
103
131
|
includeLinks: true,
|
|
104
132
|
includeColumn: true,
|
|
133
|
+
includeAttachments: true,
|
|
134
|
+
includeExternalLinks: true,
|
|
105
135
|
...options.contextOptions
|
|
106
136
|
};
|
|
107
137
|
const labels = card.labels || [];
|
|
108
138
|
const subtasks = card.subtasks || [];
|
|
109
139
|
const links = card.links || [];
|
|
140
|
+
const attachments = card.attachments || [];
|
|
141
|
+
const externalLinks = card.external_links || [];
|
|
110
142
|
const category = inferCategoryFromLabels(labels);
|
|
111
143
|
const roleFraming = getRoleFraming(category);
|
|
112
144
|
const sections = [];
|
|
@@ -143,6 +175,12 @@ ${card.description}`);
|
|
|
143
175
|
if (contextOpts.includeLinks && links.length > 0) {
|
|
144
176
|
sections.push(formatLinkedCards(links));
|
|
145
177
|
}
|
|
178
|
+
if (contextOpts.includeAttachments && attachments.length > 0) {
|
|
179
|
+
sections.push(formatAttachments(attachments));
|
|
180
|
+
}
|
|
181
|
+
if (contextOpts.includeExternalLinks && externalLinks.length > 0) {
|
|
182
|
+
sections.push(formatExternalLinks(externalLinks));
|
|
183
|
+
}
|
|
146
184
|
sections.push(`
|
|
147
185
|
## Focus Areas`);
|
|
148
186
|
roleFraming.focus.forEach((f) => {
|
|
@@ -1330,6 +1368,12 @@ class HarmonyApiClient {
|
|
|
1330
1368
|
async listProjects(workspaceId) {
|
|
1331
1369
|
return this.request("GET", `/workspaces/${workspaceId}/projects`);
|
|
1332
1370
|
}
|
|
1371
|
+
async archiveProject(projectId) {
|
|
1372
|
+
return this.request("POST", `/projects/${projectId}/archive`);
|
|
1373
|
+
}
|
|
1374
|
+
async unarchiveProject(projectId) {
|
|
1375
|
+
return this.request("POST", `/projects/${projectId}/unarchive`);
|
|
1376
|
+
}
|
|
1333
1377
|
async getBoard(projectId, options) {
|
|
1334
1378
|
const params = new URLSearchParams;
|
|
1335
1379
|
if (options?.limit !== undefined)
|
|
@@ -1399,6 +1443,12 @@ class HarmonyApiClient {
|
|
|
1399
1443
|
async getCardLinks(cardId) {
|
|
1400
1444
|
return this.request("GET", `/cards/${cardId}/links`);
|
|
1401
1445
|
}
|
|
1446
|
+
async getCardAttachments(cardId) {
|
|
1447
|
+
return this.request("GET", `/cards/${cardId}/attachments`);
|
|
1448
|
+
}
|
|
1449
|
+
async getCardExternalLinks(cardId) {
|
|
1450
|
+
return this.request("GET", `/cards/${cardId}/external-links`);
|
|
1451
|
+
}
|
|
1402
1452
|
async createColumn(projectId, name) {
|
|
1403
1453
|
return this.request("POST", "/columns", { projectId, name });
|
|
1404
1454
|
}
|
|
@@ -1710,6 +1760,30 @@ class HarmonyApiClient {
|
|
|
1710
1760
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1711
1761
|
console.debug(`[generateCardPrompt] getCardLinks failed: ${msg}`);
|
|
1712
1762
|
}
|
|
1763
|
+
try {
|
|
1764
|
+
const attachmentsResult = await this.getCardAttachments(options.cardId);
|
|
1765
|
+
cardData.attachments = (attachmentsResult.attachments ?? []).map((att) => ({
|
|
1766
|
+
id: att.id,
|
|
1767
|
+
file_name: att.file_name,
|
|
1768
|
+
file_type: att.file_type,
|
|
1769
|
+
file_size: att.file_size,
|
|
1770
|
+
signed_url: att.signed_url
|
|
1771
|
+
}));
|
|
1772
|
+
} catch (err) {
|
|
1773
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1774
|
+
console.debug(`[generateCardPrompt] getCardAttachments failed: ${msg}`);
|
|
1775
|
+
}
|
|
1776
|
+
try {
|
|
1777
|
+
const externalLinksResult = await this.getCardExternalLinks(options.cardId);
|
|
1778
|
+
cardData.external_links = (externalLinksResult.external_links ?? []).map((link) => ({
|
|
1779
|
+
id: link.id,
|
|
1780
|
+
url: link.url,
|
|
1781
|
+
title: link.title
|
|
1782
|
+
}));
|
|
1783
|
+
} catch (err) {
|
|
1784
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1785
|
+
console.debug(`[generateCardPrompt] getCardExternalLinks failed: ${msg}`);
|
|
1786
|
+
}
|
|
1713
1787
|
let columnData = null;
|
|
1714
1788
|
const projectIdForBoard = options.projectId || cardData.project_id;
|
|
1715
1789
|
if (projectIdForBoard) {
|
|
@@ -2501,6 +2575,26 @@ var TOOLS = {
|
|
|
2501
2575
|
required: ["cardId"]
|
|
2502
2576
|
}
|
|
2503
2577
|
},
|
|
2578
|
+
harmony_archive_project: {
|
|
2579
|
+
description: "Archive a project. Hides the project (and all its columns and cards) from the active workspace. Can be restored later with harmony_unarchive_project.",
|
|
2580
|
+
inputSchema: {
|
|
2581
|
+
type: "object",
|
|
2582
|
+
properties: {
|
|
2583
|
+
projectId: { type: "string", description: "Project ID to archive" }
|
|
2584
|
+
},
|
|
2585
|
+
required: ["projectId"]
|
|
2586
|
+
}
|
|
2587
|
+
},
|
|
2588
|
+
harmony_unarchive_project: {
|
|
2589
|
+
description: "Restore an archived project so it (and its content) becomes visible again.",
|
|
2590
|
+
inputSchema: {
|
|
2591
|
+
type: "object",
|
|
2592
|
+
properties: {
|
|
2593
|
+
projectId: { type: "string", description: "Project ID to unarchive" }
|
|
2594
|
+
},
|
|
2595
|
+
required: ["projectId"]
|
|
2596
|
+
}
|
|
2597
|
+
},
|
|
2504
2598
|
harmony_delete_card: {
|
|
2505
2599
|
description: "Delete a card",
|
|
2506
2600
|
inputSchema: {
|
|
@@ -2689,6 +2783,32 @@ var TOOLS = {
|
|
|
2689
2783
|
required: ["cardId"]
|
|
2690
2784
|
}
|
|
2691
2785
|
},
|
|
2786
|
+
harmony_get_card_attachments: {
|
|
2787
|
+
description: "Get all file attachments for a card with short-lived signed URLs. URLs expire after `signed_url_expires_in` seconds (default 3600). Re-fetch to refresh.",
|
|
2788
|
+
inputSchema: {
|
|
2789
|
+
type: "object",
|
|
2790
|
+
properties: {
|
|
2791
|
+
cardId: {
|
|
2792
|
+
type: "string",
|
|
2793
|
+
description: "Card UUID"
|
|
2794
|
+
}
|
|
2795
|
+
},
|
|
2796
|
+
required: ["cardId"]
|
|
2797
|
+
}
|
|
2798
|
+
},
|
|
2799
|
+
harmony_get_card_external_links: {
|
|
2800
|
+
description: "Get external URL references attached to a card (links to docs, gists, dashboards, etc.).",
|
|
2801
|
+
inputSchema: {
|
|
2802
|
+
type: "object",
|
|
2803
|
+
properties: {
|
|
2804
|
+
cardId: {
|
|
2805
|
+
type: "string",
|
|
2806
|
+
description: "Card UUID"
|
|
2807
|
+
}
|
|
2808
|
+
},
|
|
2809
|
+
required: ["cardId"]
|
|
2810
|
+
}
|
|
2811
|
+
},
|
|
2692
2812
|
harmony_create_subtask: {
|
|
2693
2813
|
description: "Create a subtask on a card",
|
|
2694
2814
|
inputSchema: {
|
|
@@ -3827,6 +3947,16 @@ async function handleToolCall(name, args, deps) {
|
|
|
3827
3947
|
const result = await client3.unarchiveCard(cardId);
|
|
3828
3948
|
return { success: true, ...result };
|
|
3829
3949
|
}
|
|
3950
|
+
case "harmony_archive_project": {
|
|
3951
|
+
const projectId = z.string().uuid().parse(args.projectId);
|
|
3952
|
+
const result = await client3.archiveProject(projectId);
|
|
3953
|
+
return { success: true, ...result };
|
|
3954
|
+
}
|
|
3955
|
+
case "harmony_unarchive_project": {
|
|
3956
|
+
const projectId = z.string().uuid().parse(args.projectId);
|
|
3957
|
+
const result = await client3.unarchiveProject(projectId);
|
|
3958
|
+
return { success: true, ...result };
|
|
3959
|
+
}
|
|
3830
3960
|
case "harmony_delete_card": {
|
|
3831
3961
|
const cardId = z.string().uuid().parse(args.cardId);
|
|
3832
3962
|
await client3.deleteCard(cardId);
|
|
@@ -3944,6 +4074,16 @@ async function handleToolCall(name, args, deps) {
|
|
|
3944
4074
|
const result = await client3.getCardLinks(cardId);
|
|
3945
4075
|
return result;
|
|
3946
4076
|
}
|
|
4077
|
+
case "harmony_get_card_attachments": {
|
|
4078
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
4079
|
+
const result = await client3.getCardAttachments(cardId);
|
|
4080
|
+
return result;
|
|
4081
|
+
}
|
|
4082
|
+
case "harmony_get_card_external_links": {
|
|
4083
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
4084
|
+
const result = await client3.getCardExternalLinks(cardId);
|
|
4085
|
+
return result;
|
|
4086
|
+
}
|
|
3947
4087
|
case "harmony_create_subtask": {
|
|
3948
4088
|
const cardId = z.string().uuid().parse(args.cardId);
|
|
3949
4089
|
const title = z.string().min(1).max(500).parse(args.title);
|
package/dist/index.js
CHANGED
|
@@ -69,6 +69,34 @@ function formatLabels(labels) {
|
|
|
69
69
|
return `
|
|
70
70
|
**Labels:** ${labels.map((l) => l.name).join(", ")}`;
|
|
71
71
|
}
|
|
72
|
+
function formatAttachments(attachments) {
|
|
73
|
+
if (!attachments || attachments.length === 0)
|
|
74
|
+
return "";
|
|
75
|
+
const lines = attachments.map((att) => {
|
|
76
|
+
const isImage = att.file_type.startsWith("image/");
|
|
77
|
+
const sizeKb = Math.max(1, Math.round(att.file_size / 1024));
|
|
78
|
+
const url = att.signed_url ?? "(signed URL unavailable)";
|
|
79
|
+
const kind = isImage ? "image" : att.file_type;
|
|
80
|
+
return ` - ${att.file_name} (${kind}, ${sizeKb} KB) — ${url}`;
|
|
81
|
+
});
|
|
82
|
+
return `
|
|
83
|
+
## Attachments
|
|
84
|
+
*Signed URLs expire in ~1 hour. Re-fetch via \`harmony_get_card_attachments\` if expired.*
|
|
85
|
+
${lines.join(`
|
|
86
|
+
`)}`;
|
|
87
|
+
}
|
|
88
|
+
function formatExternalLinks(refs) {
|
|
89
|
+
if (!refs || refs.length === 0)
|
|
90
|
+
return "";
|
|
91
|
+
const lines = refs.map((ref) => {
|
|
92
|
+
const label = ref.title?.trim() || ref.url;
|
|
93
|
+
return ` - ${label} — ${ref.url}`;
|
|
94
|
+
});
|
|
95
|
+
return `
|
|
96
|
+
## External References
|
|
97
|
+
${lines.join(`
|
|
98
|
+
`)}`;
|
|
99
|
+
}
|
|
72
100
|
function formatLinkedCards(links) {
|
|
73
101
|
if (!links || links.length === 0)
|
|
74
102
|
return "";
|
|
@@ -102,11 +130,15 @@ function generatePrompt(options) {
|
|
|
102
130
|
includePriority: true,
|
|
103
131
|
includeLinks: true,
|
|
104
132
|
includeColumn: true,
|
|
133
|
+
includeAttachments: true,
|
|
134
|
+
includeExternalLinks: true,
|
|
105
135
|
...options.contextOptions
|
|
106
136
|
};
|
|
107
137
|
const labels = card.labels || [];
|
|
108
138
|
const subtasks = card.subtasks || [];
|
|
109
139
|
const links = card.links || [];
|
|
140
|
+
const attachments = card.attachments || [];
|
|
141
|
+
const externalLinks = card.external_links || [];
|
|
110
142
|
const category = inferCategoryFromLabels(labels);
|
|
111
143
|
const roleFraming = getRoleFraming(category);
|
|
112
144
|
const sections = [];
|
|
@@ -143,6 +175,12 @@ ${card.description}`);
|
|
|
143
175
|
if (contextOpts.includeLinks && links.length > 0) {
|
|
144
176
|
sections.push(formatLinkedCards(links));
|
|
145
177
|
}
|
|
178
|
+
if (contextOpts.includeAttachments && attachments.length > 0) {
|
|
179
|
+
sections.push(formatAttachments(attachments));
|
|
180
|
+
}
|
|
181
|
+
if (contextOpts.includeExternalLinks && externalLinks.length > 0) {
|
|
182
|
+
sections.push(formatExternalLinks(externalLinks));
|
|
183
|
+
}
|
|
146
184
|
sections.push(`
|
|
147
185
|
## Focus Areas`);
|
|
148
186
|
roleFraming.focus.forEach((f) => {
|
|
@@ -1326,6 +1364,12 @@ class HarmonyApiClient {
|
|
|
1326
1364
|
async listProjects(workspaceId) {
|
|
1327
1365
|
return this.request("GET", `/workspaces/${workspaceId}/projects`);
|
|
1328
1366
|
}
|
|
1367
|
+
async archiveProject(projectId) {
|
|
1368
|
+
return this.request("POST", `/projects/${projectId}/archive`);
|
|
1369
|
+
}
|
|
1370
|
+
async unarchiveProject(projectId) {
|
|
1371
|
+
return this.request("POST", `/projects/${projectId}/unarchive`);
|
|
1372
|
+
}
|
|
1329
1373
|
async getBoard(projectId, options) {
|
|
1330
1374
|
const params = new URLSearchParams;
|
|
1331
1375
|
if (options?.limit !== undefined)
|
|
@@ -1395,6 +1439,12 @@ class HarmonyApiClient {
|
|
|
1395
1439
|
async getCardLinks(cardId) {
|
|
1396
1440
|
return this.request("GET", `/cards/${cardId}/links`);
|
|
1397
1441
|
}
|
|
1442
|
+
async getCardAttachments(cardId) {
|
|
1443
|
+
return this.request("GET", `/cards/${cardId}/attachments`);
|
|
1444
|
+
}
|
|
1445
|
+
async getCardExternalLinks(cardId) {
|
|
1446
|
+
return this.request("GET", `/cards/${cardId}/external-links`);
|
|
1447
|
+
}
|
|
1398
1448
|
async createColumn(projectId, name) {
|
|
1399
1449
|
return this.request("POST", "/columns", { projectId, name });
|
|
1400
1450
|
}
|
|
@@ -1706,6 +1756,30 @@ class HarmonyApiClient {
|
|
|
1706
1756
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1707
1757
|
console.debug(`[generateCardPrompt] getCardLinks failed: ${msg}`);
|
|
1708
1758
|
}
|
|
1759
|
+
try {
|
|
1760
|
+
const attachmentsResult = await this.getCardAttachments(options.cardId);
|
|
1761
|
+
cardData.attachments = (attachmentsResult.attachments ?? []).map((att) => ({
|
|
1762
|
+
id: att.id,
|
|
1763
|
+
file_name: att.file_name,
|
|
1764
|
+
file_type: att.file_type,
|
|
1765
|
+
file_size: att.file_size,
|
|
1766
|
+
signed_url: att.signed_url
|
|
1767
|
+
}));
|
|
1768
|
+
} catch (err) {
|
|
1769
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1770
|
+
console.debug(`[generateCardPrompt] getCardAttachments failed: ${msg}`);
|
|
1771
|
+
}
|
|
1772
|
+
try {
|
|
1773
|
+
const externalLinksResult = await this.getCardExternalLinks(options.cardId);
|
|
1774
|
+
cardData.external_links = (externalLinksResult.external_links ?? []).map((link) => ({
|
|
1775
|
+
id: link.id,
|
|
1776
|
+
url: link.url,
|
|
1777
|
+
title: link.title
|
|
1778
|
+
}));
|
|
1779
|
+
} catch (err) {
|
|
1780
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1781
|
+
console.debug(`[generateCardPrompt] getCardExternalLinks failed: ${msg}`);
|
|
1782
|
+
}
|
|
1709
1783
|
let columnData = null;
|
|
1710
1784
|
const projectIdForBoard = options.projectId || cardData.project_id;
|
|
1711
1785
|
if (projectIdForBoard) {
|
|
@@ -2497,6 +2571,26 @@ var TOOLS = {
|
|
|
2497
2571
|
required: ["cardId"]
|
|
2498
2572
|
}
|
|
2499
2573
|
},
|
|
2574
|
+
harmony_archive_project: {
|
|
2575
|
+
description: "Archive a project. Hides the project (and all its columns and cards) from the active workspace. Can be restored later with harmony_unarchive_project.",
|
|
2576
|
+
inputSchema: {
|
|
2577
|
+
type: "object",
|
|
2578
|
+
properties: {
|
|
2579
|
+
projectId: { type: "string", description: "Project ID to archive" }
|
|
2580
|
+
},
|
|
2581
|
+
required: ["projectId"]
|
|
2582
|
+
}
|
|
2583
|
+
},
|
|
2584
|
+
harmony_unarchive_project: {
|
|
2585
|
+
description: "Restore an archived project so it (and its content) becomes visible again.",
|
|
2586
|
+
inputSchema: {
|
|
2587
|
+
type: "object",
|
|
2588
|
+
properties: {
|
|
2589
|
+
projectId: { type: "string", description: "Project ID to unarchive" }
|
|
2590
|
+
},
|
|
2591
|
+
required: ["projectId"]
|
|
2592
|
+
}
|
|
2593
|
+
},
|
|
2500
2594
|
harmony_delete_card: {
|
|
2501
2595
|
description: "Delete a card",
|
|
2502
2596
|
inputSchema: {
|
|
@@ -2685,6 +2779,32 @@ var TOOLS = {
|
|
|
2685
2779
|
required: ["cardId"]
|
|
2686
2780
|
}
|
|
2687
2781
|
},
|
|
2782
|
+
harmony_get_card_attachments: {
|
|
2783
|
+
description: "Get all file attachments for a card with short-lived signed URLs. URLs expire after `signed_url_expires_in` seconds (default 3600). Re-fetch to refresh.",
|
|
2784
|
+
inputSchema: {
|
|
2785
|
+
type: "object",
|
|
2786
|
+
properties: {
|
|
2787
|
+
cardId: {
|
|
2788
|
+
type: "string",
|
|
2789
|
+
description: "Card UUID"
|
|
2790
|
+
}
|
|
2791
|
+
},
|
|
2792
|
+
required: ["cardId"]
|
|
2793
|
+
}
|
|
2794
|
+
},
|
|
2795
|
+
harmony_get_card_external_links: {
|
|
2796
|
+
description: "Get external URL references attached to a card (links to docs, gists, dashboards, etc.).",
|
|
2797
|
+
inputSchema: {
|
|
2798
|
+
type: "object",
|
|
2799
|
+
properties: {
|
|
2800
|
+
cardId: {
|
|
2801
|
+
type: "string",
|
|
2802
|
+
description: "Card UUID"
|
|
2803
|
+
}
|
|
2804
|
+
},
|
|
2805
|
+
required: ["cardId"]
|
|
2806
|
+
}
|
|
2807
|
+
},
|
|
2688
2808
|
harmony_create_subtask: {
|
|
2689
2809
|
description: "Create a subtask on a card",
|
|
2690
2810
|
inputSchema: {
|
|
@@ -3823,6 +3943,16 @@ async function handleToolCall(name, args, deps) {
|
|
|
3823
3943
|
const result = await client3.unarchiveCard(cardId);
|
|
3824
3944
|
return { success: true, ...result };
|
|
3825
3945
|
}
|
|
3946
|
+
case "harmony_archive_project": {
|
|
3947
|
+
const projectId = z.string().uuid().parse(args.projectId);
|
|
3948
|
+
const result = await client3.archiveProject(projectId);
|
|
3949
|
+
return { success: true, ...result };
|
|
3950
|
+
}
|
|
3951
|
+
case "harmony_unarchive_project": {
|
|
3952
|
+
const projectId = z.string().uuid().parse(args.projectId);
|
|
3953
|
+
const result = await client3.unarchiveProject(projectId);
|
|
3954
|
+
return { success: true, ...result };
|
|
3955
|
+
}
|
|
3826
3956
|
case "harmony_delete_card": {
|
|
3827
3957
|
const cardId = z.string().uuid().parse(args.cardId);
|
|
3828
3958
|
await client3.deleteCard(cardId);
|
|
@@ -3940,6 +4070,16 @@ async function handleToolCall(name, args, deps) {
|
|
|
3940
4070
|
const result = await client3.getCardLinks(cardId);
|
|
3941
4071
|
return result;
|
|
3942
4072
|
}
|
|
4073
|
+
case "harmony_get_card_attachments": {
|
|
4074
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
4075
|
+
const result = await client3.getCardAttachments(cardId);
|
|
4076
|
+
return result;
|
|
4077
|
+
}
|
|
4078
|
+
case "harmony_get_card_external_links": {
|
|
4079
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
4080
|
+
const result = await client3.getCardExternalLinks(cardId);
|
|
4081
|
+
return result;
|
|
4082
|
+
}
|
|
3943
4083
|
case "harmony_create_subtask": {
|
|
3944
4084
|
const cardId = z.string().uuid().parse(args.cardId);
|
|
3945
4085
|
const title = z.string().min(1).max(500).parse(args.title);
|
package/dist/lib/api-client.js
CHANGED
|
@@ -66,6 +66,34 @@ function formatLabels(labels) {
|
|
|
66
66
|
return `
|
|
67
67
|
**Labels:** ${labels.map((l) => l.name).join(", ")}`;
|
|
68
68
|
}
|
|
69
|
+
function formatAttachments(attachments) {
|
|
70
|
+
if (!attachments || attachments.length === 0)
|
|
71
|
+
return "";
|
|
72
|
+
const lines = attachments.map((att) => {
|
|
73
|
+
const isImage = att.file_type.startsWith("image/");
|
|
74
|
+
const sizeKb = Math.max(1, Math.round(att.file_size / 1024));
|
|
75
|
+
const url = att.signed_url ?? "(signed URL unavailable)";
|
|
76
|
+
const kind = isImage ? "image" : att.file_type;
|
|
77
|
+
return ` - ${att.file_name} (${kind}, ${sizeKb} KB) — ${url}`;
|
|
78
|
+
});
|
|
79
|
+
return `
|
|
80
|
+
## Attachments
|
|
81
|
+
*Signed URLs expire in ~1 hour. Re-fetch via \`harmony_get_card_attachments\` if expired.*
|
|
82
|
+
${lines.join(`
|
|
83
|
+
`)}`;
|
|
84
|
+
}
|
|
85
|
+
function formatExternalLinks(refs) {
|
|
86
|
+
if (!refs || refs.length === 0)
|
|
87
|
+
return "";
|
|
88
|
+
const lines = refs.map((ref) => {
|
|
89
|
+
const label = ref.title?.trim() || ref.url;
|
|
90
|
+
return ` - ${label} — ${ref.url}`;
|
|
91
|
+
});
|
|
92
|
+
return `
|
|
93
|
+
## External References
|
|
94
|
+
${lines.join(`
|
|
95
|
+
`)}`;
|
|
96
|
+
}
|
|
69
97
|
function formatLinkedCards(links) {
|
|
70
98
|
if (!links || links.length === 0)
|
|
71
99
|
return "";
|
|
@@ -99,11 +127,15 @@ function generatePrompt(options) {
|
|
|
99
127
|
includePriority: true,
|
|
100
128
|
includeLinks: true,
|
|
101
129
|
includeColumn: true,
|
|
130
|
+
includeAttachments: true,
|
|
131
|
+
includeExternalLinks: true,
|
|
102
132
|
...options.contextOptions
|
|
103
133
|
};
|
|
104
134
|
const labels = card.labels || [];
|
|
105
135
|
const subtasks = card.subtasks || [];
|
|
106
136
|
const links = card.links || [];
|
|
137
|
+
const attachments = card.attachments || [];
|
|
138
|
+
const externalLinks = card.external_links || [];
|
|
107
139
|
const category = inferCategoryFromLabels(labels);
|
|
108
140
|
const roleFraming = getRoleFraming(category);
|
|
109
141
|
const sections = [];
|
|
@@ -140,6 +172,12 @@ ${card.description}`);
|
|
|
140
172
|
if (contextOpts.includeLinks && links.length > 0) {
|
|
141
173
|
sections.push(formatLinkedCards(links));
|
|
142
174
|
}
|
|
175
|
+
if (contextOpts.includeAttachments && attachments.length > 0) {
|
|
176
|
+
sections.push(formatAttachments(attachments));
|
|
177
|
+
}
|
|
178
|
+
if (contextOpts.includeExternalLinks && externalLinks.length > 0) {
|
|
179
|
+
sections.push(formatExternalLinks(externalLinks));
|
|
180
|
+
}
|
|
143
181
|
sections.push(`
|
|
144
182
|
## Focus Areas`);
|
|
145
183
|
roleFraming.focus.forEach((f) => {
|
|
@@ -933,6 +971,12 @@ class HarmonyApiClient {
|
|
|
933
971
|
async listProjects(workspaceId) {
|
|
934
972
|
return this.request("GET", `/workspaces/${workspaceId}/projects`);
|
|
935
973
|
}
|
|
974
|
+
async archiveProject(projectId) {
|
|
975
|
+
return this.request("POST", `/projects/${projectId}/archive`);
|
|
976
|
+
}
|
|
977
|
+
async unarchiveProject(projectId) {
|
|
978
|
+
return this.request("POST", `/projects/${projectId}/unarchive`);
|
|
979
|
+
}
|
|
936
980
|
async getBoard(projectId, options) {
|
|
937
981
|
const params = new URLSearchParams;
|
|
938
982
|
if (options?.limit !== undefined)
|
|
@@ -1002,6 +1046,12 @@ class HarmonyApiClient {
|
|
|
1002
1046
|
async getCardLinks(cardId) {
|
|
1003
1047
|
return this.request("GET", `/cards/${cardId}/links`);
|
|
1004
1048
|
}
|
|
1049
|
+
async getCardAttachments(cardId) {
|
|
1050
|
+
return this.request("GET", `/cards/${cardId}/attachments`);
|
|
1051
|
+
}
|
|
1052
|
+
async getCardExternalLinks(cardId) {
|
|
1053
|
+
return this.request("GET", `/cards/${cardId}/external-links`);
|
|
1054
|
+
}
|
|
1005
1055
|
async createColumn(projectId, name) {
|
|
1006
1056
|
return this.request("POST", "/columns", { projectId, name });
|
|
1007
1057
|
}
|
|
@@ -1313,6 +1363,30 @@ class HarmonyApiClient {
|
|
|
1313
1363
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1314
1364
|
console.debug(`[generateCardPrompt] getCardLinks failed: ${msg}`);
|
|
1315
1365
|
}
|
|
1366
|
+
try {
|
|
1367
|
+
const attachmentsResult = await this.getCardAttachments(options.cardId);
|
|
1368
|
+
cardData.attachments = (attachmentsResult.attachments ?? []).map((att) => ({
|
|
1369
|
+
id: att.id,
|
|
1370
|
+
file_name: att.file_name,
|
|
1371
|
+
file_type: att.file_type,
|
|
1372
|
+
file_size: att.file_size,
|
|
1373
|
+
signed_url: att.signed_url
|
|
1374
|
+
}));
|
|
1375
|
+
} catch (err) {
|
|
1376
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1377
|
+
console.debug(`[generateCardPrompt] getCardAttachments failed: ${msg}`);
|
|
1378
|
+
}
|
|
1379
|
+
try {
|
|
1380
|
+
const externalLinksResult = await this.getCardExternalLinks(options.cardId);
|
|
1381
|
+
cardData.external_links = (externalLinksResult.external_links ?? []).map((link) => ({
|
|
1382
|
+
id: link.id,
|
|
1383
|
+
url: link.url,
|
|
1384
|
+
title: link.title
|
|
1385
|
+
}));
|
|
1386
|
+
} catch (err) {
|
|
1387
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1388
|
+
console.debug(`[generateCardPrompt] getCardExternalLinks failed: ${msg}`);
|
|
1389
|
+
}
|
|
1316
1390
|
let columnData = null;
|
|
1317
1391
|
const projectIdForBoard = options.projectId || cardData.project_id;
|
|
1318
1392
|
if (projectIdForBoard) {
|
package/package.json
CHANGED
package/src/api-client.ts
CHANGED
|
@@ -124,6 +124,28 @@ export class HarmonyUnauthorizedError extends Error {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
export interface CardAttachment {
|
|
128
|
+
id: string;
|
|
129
|
+
card_id: string;
|
|
130
|
+
file_name: string;
|
|
131
|
+
file_type: string;
|
|
132
|
+
file_size: number;
|
|
133
|
+
storage_path: string;
|
|
134
|
+
uploaded_by: string | null;
|
|
135
|
+
created_at: string;
|
|
136
|
+
signed_url: string | null;
|
|
137
|
+
signed_url_expires_in: number;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface CardExternalLinkRow {
|
|
141
|
+
id: string;
|
|
142
|
+
card_id: string;
|
|
143
|
+
url: string;
|
|
144
|
+
title: string | null;
|
|
145
|
+
created_by: string | null;
|
|
146
|
+
created_at: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
127
149
|
export class HarmonyApiClient {
|
|
128
150
|
private apiKey: string;
|
|
129
151
|
private apiUrl: string;
|
|
@@ -376,6 +398,14 @@ export class HarmonyApiClient {
|
|
|
376
398
|
return this.request("GET", `/workspaces/${workspaceId}/projects`);
|
|
377
399
|
}
|
|
378
400
|
|
|
401
|
+
async archiveProject(projectId: string): Promise<{ project: unknown }> {
|
|
402
|
+
return this.request("POST", `/projects/${projectId}/archive`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async unarchiveProject(projectId: string): Promise<{ project: unknown }> {
|
|
406
|
+
return this.request("POST", `/projects/${projectId}/unarchive`);
|
|
407
|
+
}
|
|
408
|
+
|
|
379
409
|
async getBoard(
|
|
380
410
|
projectId: string,
|
|
381
411
|
options?: {
|
|
@@ -524,6 +554,18 @@ export class HarmonyApiClient {
|
|
|
524
554
|
return this.request("GET", `/cards/${cardId}/links`);
|
|
525
555
|
}
|
|
526
556
|
|
|
557
|
+
async getCardAttachments(
|
|
558
|
+
cardId: string,
|
|
559
|
+
): Promise<{ attachments: CardAttachment[] }> {
|
|
560
|
+
return this.request("GET", `/cards/${cardId}/attachments`);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
async getCardExternalLinks(
|
|
564
|
+
cardId: string,
|
|
565
|
+
): Promise<{ external_links: CardExternalLinkRow[] }> {
|
|
566
|
+
return this.request("GET", `/cards/${cardId}/external-links`);
|
|
567
|
+
}
|
|
568
|
+
|
|
527
569
|
// ============ COLUMN OPERATIONS ============
|
|
528
570
|
|
|
529
571
|
async createColumn(
|
|
@@ -1282,6 +1324,40 @@ export class HarmonyApiClient {
|
|
|
1282
1324
|
console.debug(`[generateCardPrompt] getCardLinks failed: ${msg}`);
|
|
1283
1325
|
}
|
|
1284
1326
|
|
|
1327
|
+
// Fetch attachments + external links. Best-effort — never break prompt
|
|
1328
|
+
// generation if storage signing or the underlying tables fail.
|
|
1329
|
+
try {
|
|
1330
|
+
const attachmentsResult = await this.getCardAttachments(options.cardId);
|
|
1331
|
+
cardData.attachments = (attachmentsResult.attachments ?? []).map(
|
|
1332
|
+
(att) => ({
|
|
1333
|
+
id: att.id,
|
|
1334
|
+
file_name: att.file_name,
|
|
1335
|
+
file_type: att.file_type,
|
|
1336
|
+
file_size: att.file_size,
|
|
1337
|
+
signed_url: att.signed_url,
|
|
1338
|
+
}),
|
|
1339
|
+
);
|
|
1340
|
+
} catch (err) {
|
|
1341
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1342
|
+
console.debug(`[generateCardPrompt] getCardAttachments failed: ${msg}`);
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
try {
|
|
1346
|
+
const externalLinksResult = await this.getCardExternalLinks(
|
|
1347
|
+
options.cardId,
|
|
1348
|
+
);
|
|
1349
|
+
cardData.external_links = (externalLinksResult.external_links ?? []).map(
|
|
1350
|
+
(link) => ({
|
|
1351
|
+
id: link.id,
|
|
1352
|
+
url: link.url,
|
|
1353
|
+
title: link.title,
|
|
1354
|
+
}),
|
|
1355
|
+
);
|
|
1356
|
+
} catch (err) {
|
|
1357
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1358
|
+
console.debug(`[generateCardPrompt] getCardExternalLinks failed: ${msg}`);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1285
1361
|
// Try to get column info
|
|
1286
1362
|
let columnData: { name: string } | null = null;
|
|
1287
1363
|
const projectIdForBoard = options.projectId || cardData.project_id;
|
|
@@ -1395,6 +1471,18 @@ interface CardPromptData {
|
|
|
1395
1471
|
direction: "outgoing" | "incoming";
|
|
1396
1472
|
}>;
|
|
1397
1473
|
assignee?: { full_name?: string; email: string } | null;
|
|
1474
|
+
attachments?: Array<{
|
|
1475
|
+
id: string;
|
|
1476
|
+
file_name: string;
|
|
1477
|
+
file_type: string;
|
|
1478
|
+
file_size: number;
|
|
1479
|
+
signed_url: string | null;
|
|
1480
|
+
}>;
|
|
1481
|
+
external_links?: Array<{
|
|
1482
|
+
id: string;
|
|
1483
|
+
url: string;
|
|
1484
|
+
title?: string | null;
|
|
1485
|
+
}>;
|
|
1398
1486
|
column_id?: string;
|
|
1399
1487
|
project_id?: string;
|
|
1400
1488
|
}
|
package/src/prompt-builder.ts
CHANGED
|
@@ -44,6 +44,8 @@ export interface PromptContextOptions {
|
|
|
44
44
|
includePriority: boolean;
|
|
45
45
|
includeLinks: boolean;
|
|
46
46
|
includeColumn: boolean;
|
|
47
|
+
includeAttachments: boolean;
|
|
48
|
+
includeExternalLinks: boolean;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export interface RoleFraming {
|
|
@@ -307,6 +309,27 @@ function formatLabels(labels: Array<{ name: string }>): string {
|
|
|
307
309
|
return `\n**Labels:** ${labels.map((l) => l.name).join(", ")}`;
|
|
308
310
|
}
|
|
309
311
|
|
|
312
|
+
function formatAttachments(attachments: CardAttachmentRef[]): string {
|
|
313
|
+
if (!attachments || attachments.length === 0) return "";
|
|
314
|
+
const lines = attachments.map((att) => {
|
|
315
|
+
const isImage = att.file_type.startsWith("image/");
|
|
316
|
+
const sizeKb = Math.max(1, Math.round(att.file_size / 1024));
|
|
317
|
+
const url = att.signed_url ?? "(signed URL unavailable)";
|
|
318
|
+
const kind = isImage ? "image" : att.file_type;
|
|
319
|
+
return ` - ${att.file_name} (${kind}, ${sizeKb} KB) — ${url}`;
|
|
320
|
+
});
|
|
321
|
+
return `\n## Attachments\n*Signed URLs expire in ~1 hour. Re-fetch via \`harmony_get_card_attachments\` if expired.*\n${lines.join("\n")}`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function formatExternalLinks(refs: CardExternalLinkRef[]): string {
|
|
325
|
+
if (!refs || refs.length === 0) return "";
|
|
326
|
+
const lines = refs.map((ref) => {
|
|
327
|
+
const label = ref.title?.trim() || ref.url;
|
|
328
|
+
return ` - ${label} — ${ref.url}`;
|
|
329
|
+
});
|
|
330
|
+
return `\n## External References\n${lines.join("\n")}`;
|
|
331
|
+
}
|
|
332
|
+
|
|
310
333
|
/**
|
|
311
334
|
* Format linked cards for prompt
|
|
312
335
|
*/
|
|
@@ -325,6 +348,20 @@ function formatLinkedCards(
|
|
|
325
348
|
return `\n## Related Cards\n${lines.join("\n")}`;
|
|
326
349
|
}
|
|
327
350
|
|
|
351
|
+
export interface CardAttachmentRef {
|
|
352
|
+
id: string;
|
|
353
|
+
file_name: string;
|
|
354
|
+
file_type: string;
|
|
355
|
+
file_size: number;
|
|
356
|
+
signed_url: string | null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export interface CardExternalLinkRef {
|
|
360
|
+
id: string;
|
|
361
|
+
url: string;
|
|
362
|
+
title?: string | null;
|
|
363
|
+
}
|
|
364
|
+
|
|
328
365
|
export interface CardData {
|
|
329
366
|
id: string;
|
|
330
367
|
short_id: number;
|
|
@@ -341,6 +378,8 @@ export interface CardData {
|
|
|
341
378
|
direction: "outgoing" | "incoming";
|
|
342
379
|
}>;
|
|
343
380
|
assignee?: { full_name?: string; email: string } | null;
|
|
381
|
+
attachments?: CardAttachmentRef[];
|
|
382
|
+
external_links?: CardExternalLinkRef[];
|
|
344
383
|
}
|
|
345
384
|
|
|
346
385
|
export interface ColumnData {
|
|
@@ -397,12 +436,16 @@ export function generatePrompt(
|
|
|
397
436
|
includePriority: true,
|
|
398
437
|
includeLinks: true,
|
|
399
438
|
includeColumn: true,
|
|
439
|
+
includeAttachments: true,
|
|
440
|
+
includeExternalLinks: true,
|
|
400
441
|
...options.contextOptions,
|
|
401
442
|
};
|
|
402
443
|
|
|
403
444
|
const labels = card.labels || [];
|
|
404
445
|
const subtasks = card.subtasks || [];
|
|
405
446
|
const links = card.links || [];
|
|
447
|
+
const attachments = card.attachments || [];
|
|
448
|
+
const externalLinks = card.external_links || [];
|
|
406
449
|
|
|
407
450
|
const category = inferCategoryFromLabels(labels);
|
|
408
451
|
const roleFraming = getRoleFraming(category);
|
|
@@ -456,6 +499,16 @@ export function generatePrompt(
|
|
|
456
499
|
sections.push(formatLinkedCards(links));
|
|
457
500
|
}
|
|
458
501
|
|
|
502
|
+
// Attachments (signed URLs) — fetchable image/file references
|
|
503
|
+
if (contextOpts.includeAttachments && attachments.length > 0) {
|
|
504
|
+
sections.push(formatAttachments(attachments));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// External URL references (docs, gists, dashboards)
|
|
508
|
+
if (contextOpts.includeExternalLinks && externalLinks.length > 0) {
|
|
509
|
+
sections.push(formatExternalLinks(externalLinks));
|
|
510
|
+
}
|
|
511
|
+
|
|
459
512
|
// Focus areas
|
|
460
513
|
sections.push(`\n## Focus Areas`);
|
|
461
514
|
roleFraming.focus.forEach((f) => {
|
package/src/server.ts
CHANGED
|
@@ -348,6 +348,28 @@ export const TOOLS = {
|
|
|
348
348
|
required: ["cardId"],
|
|
349
349
|
},
|
|
350
350
|
},
|
|
351
|
+
harmony_archive_project: {
|
|
352
|
+
description:
|
|
353
|
+
"Archive a project. Hides the project (and all its columns and cards) from the active workspace. Can be restored later with harmony_unarchive_project.",
|
|
354
|
+
inputSchema: {
|
|
355
|
+
type: "object",
|
|
356
|
+
properties: {
|
|
357
|
+
projectId: { type: "string", description: "Project ID to archive" },
|
|
358
|
+
},
|
|
359
|
+
required: ["projectId"],
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
harmony_unarchive_project: {
|
|
363
|
+
description:
|
|
364
|
+
"Restore an archived project so it (and its content) becomes visible again.",
|
|
365
|
+
inputSchema: {
|
|
366
|
+
type: "object",
|
|
367
|
+
properties: {
|
|
368
|
+
projectId: { type: "string", description: "Project ID to unarchive" },
|
|
369
|
+
},
|
|
370
|
+
required: ["projectId"],
|
|
371
|
+
},
|
|
372
|
+
},
|
|
351
373
|
harmony_delete_card: {
|
|
352
374
|
description: "Delete a card",
|
|
353
375
|
inputSchema: {
|
|
@@ -546,6 +568,34 @@ export const TOOLS = {
|
|
|
546
568
|
required: ["cardId"],
|
|
547
569
|
},
|
|
548
570
|
},
|
|
571
|
+
harmony_get_card_attachments: {
|
|
572
|
+
description:
|
|
573
|
+
"Get all file attachments for a card with short-lived signed URLs. URLs expire after `signed_url_expires_in` seconds (default 3600). Re-fetch to refresh.",
|
|
574
|
+
inputSchema: {
|
|
575
|
+
type: "object",
|
|
576
|
+
properties: {
|
|
577
|
+
cardId: {
|
|
578
|
+
type: "string",
|
|
579
|
+
description: "Card UUID",
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
required: ["cardId"],
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
harmony_get_card_external_links: {
|
|
586
|
+
description:
|
|
587
|
+
"Get external URL references attached to a card (links to docs, gists, dashboards, etc.).",
|
|
588
|
+
inputSchema: {
|
|
589
|
+
type: "object",
|
|
590
|
+
properties: {
|
|
591
|
+
cardId: {
|
|
592
|
+
type: "string",
|
|
593
|
+
description: "Card UUID",
|
|
594
|
+
},
|
|
595
|
+
},
|
|
596
|
+
required: ["cardId"],
|
|
597
|
+
},
|
|
598
|
+
},
|
|
549
599
|
|
|
550
600
|
// Subtask operations
|
|
551
601
|
harmony_create_subtask: {
|
|
@@ -1908,6 +1958,18 @@ async function handleToolCall(
|
|
|
1908
1958
|
return { success: true, ...result };
|
|
1909
1959
|
}
|
|
1910
1960
|
|
|
1961
|
+
case "harmony_archive_project": {
|
|
1962
|
+
const projectId = z.string().uuid().parse(args.projectId);
|
|
1963
|
+
const result = await client.archiveProject(projectId);
|
|
1964
|
+
return { success: true, ...result };
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
case "harmony_unarchive_project": {
|
|
1968
|
+
const projectId = z.string().uuid().parse(args.projectId);
|
|
1969
|
+
const result = await client.unarchiveProject(projectId);
|
|
1970
|
+
return { success: true, ...result };
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1911
1973
|
case "harmony_delete_card": {
|
|
1912
1974
|
const cardId = z.string().uuid().parse(args.cardId);
|
|
1913
1975
|
await client.deleteCard(cardId);
|
|
@@ -2074,6 +2136,18 @@ async function handleToolCall(
|
|
|
2074
2136
|
return result;
|
|
2075
2137
|
}
|
|
2076
2138
|
|
|
2139
|
+
case "harmony_get_card_attachments": {
|
|
2140
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
2141
|
+
const result = await client.getCardAttachments(cardId);
|
|
2142
|
+
return result;
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
case "harmony_get_card_external_links": {
|
|
2146
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
2147
|
+
const result = await client.getCardExternalLinks(cardId);
|
|
2148
|
+
return result;
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2077
2151
|
// Subtask operations
|
|
2078
2152
|
case "harmony_create_subtask": {
|
|
2079
2153
|
const cardId = z.string().uuid().parse(args.cardId);
|