@gethmy/mcp 2.11.2 → 2.13.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 +546 -18
- package/dist/index.js +544 -16
- package/dist/lib/api-client.js +47 -0
- package/package.json +1 -1
- package/src/api-client.ts +173 -0
- package/src/server.ts +666 -17
package/dist/index.js
CHANGED
|
@@ -985,6 +985,7 @@ var init_oauth_refresh = __esm(() => {
|
|
|
985
985
|
});
|
|
986
986
|
|
|
987
987
|
// src/server.ts
|
|
988
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
988
989
|
import { readFile } from "node:fs/promises";
|
|
989
990
|
import { basename } from "node:path";
|
|
990
991
|
// ../memory/dist/sync.js
|
|
@@ -1491,6 +1492,9 @@ var TIMINGS = {
|
|
|
1491
1492
|
QUERY_STALE_TIME: 1000 * 60 * 5,
|
|
1492
1493
|
QUERY_GC_TIME: 1000 * 60 * 60 * 24
|
|
1493
1494
|
};
|
|
1495
|
+
// ../harmony-shared/dist/stageHandoff.js
|
|
1496
|
+
var HANDOFF_MARKER = "harmony:stage-handoff";
|
|
1497
|
+
var HANDOFF_BLOCK_RE = new RegExp("```json\\s*\\n//\\s*" + HANDOFF_MARKER + "\\s*\\n([\\s\\S]*?)\\n```", "m");
|
|
1494
1498
|
// src/api-client.ts
|
|
1495
1499
|
init_config();
|
|
1496
1500
|
var RETRY_CONFIG = {
|
|
@@ -1850,9 +1854,29 @@ class HarmonyApiClient {
|
|
|
1850
1854
|
async uploadCardAttachment(cardId, data) {
|
|
1851
1855
|
return this.request("POST", `/cards/${cardId}/attachments`, data);
|
|
1852
1856
|
}
|
|
1857
|
+
async requestCardAttachmentUploadUrl(cardId, data) {
|
|
1858
|
+
return this.request("POST", `/cards/${cardId}/attachment-upload-url`, data);
|
|
1859
|
+
}
|
|
1860
|
+
async finalizeCardAttachment(cardId, data) {
|
|
1861
|
+
return this.request("POST", `/cards/${cardId}/attachments/finalize`, data);
|
|
1862
|
+
}
|
|
1853
1863
|
async getCardExternalLinks(cardId) {
|
|
1854
1864
|
return this.request("GET", `/cards/${cardId}/external-links`);
|
|
1855
1865
|
}
|
|
1866
|
+
async uploadArtifact(data) {
|
|
1867
|
+
return this.request("POST", "/artifacts", data);
|
|
1868
|
+
}
|
|
1869
|
+
async requestArtifactUploadUrl(data) {
|
|
1870
|
+
return this.request("POST", "/artifacts/upload-url", data);
|
|
1871
|
+
}
|
|
1872
|
+
async finalizeArtifact(data) {
|
|
1873
|
+
return this.request("POST", "/artifacts/finalize", data);
|
|
1874
|
+
}
|
|
1875
|
+
async createArtifactShareLink(artifactId, expiresAt) {
|
|
1876
|
+
return this.request("POST", `/artifacts/${artifactId}/share`, {
|
|
1877
|
+
expires_at: expiresAt
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1856
1880
|
async classifyCard(cardId) {
|
|
1857
1881
|
return this.request("POST", `/cards/${cardId}/classify`);
|
|
1858
1882
|
}
|
|
@@ -2319,6 +2343,30 @@ ${planContent.trim()}`;
|
|
|
2319
2343
|
title: cardData.title
|
|
2320
2344
|
};
|
|
2321
2345
|
}
|
|
2346
|
+
async listPlaybooks(workspaceId) {
|
|
2347
|
+
return this.request("GET", `/playbooks?workspaceId=${encodeURIComponent(workspaceId)}`);
|
|
2348
|
+
}
|
|
2349
|
+
async getPlaybook(playbookId) {
|
|
2350
|
+
return this.request("GET", `/playbooks/${playbookId}`);
|
|
2351
|
+
}
|
|
2352
|
+
async getPlaybookVersion(playbookId, version) {
|
|
2353
|
+
return this.request("GET", `/playbooks/${encodeURIComponent(playbookId)}/versions/${version}`);
|
|
2354
|
+
}
|
|
2355
|
+
async recordStageGateEvidence(insert) {
|
|
2356
|
+
return this.request("POST", `/cards/${encodeURIComponent(insert.card_id)}/stage-gate-evidence`, insert);
|
|
2357
|
+
}
|
|
2358
|
+
async createPlaybook(data) {
|
|
2359
|
+
return this.request("POST", "/playbooks", data);
|
|
2360
|
+
}
|
|
2361
|
+
async updatePlaybook(playbookId, updates) {
|
|
2362
|
+
return this.request("PATCH", `/playbooks/${playbookId}`, updates);
|
|
2363
|
+
}
|
|
2364
|
+
async runPlaybook(playbookId) {
|
|
2365
|
+
return this.request("POST", `/playbooks/${playbookId}/run`);
|
|
2366
|
+
}
|
|
2367
|
+
async savePlaybookFromCard(data) {
|
|
2368
|
+
return this.request("POST", "/playbooks/from-card", data);
|
|
2369
|
+
}
|
|
2322
2370
|
}
|
|
2323
2371
|
var _promptModules = null;
|
|
2324
2372
|
async function loadPromptModules() {
|
|
@@ -3405,6 +3453,52 @@ async function refreshSkills(opts = {}) {
|
|
|
3405
3453
|
}
|
|
3406
3454
|
|
|
3407
3455
|
// src/server.ts
|
|
3456
|
+
var MAX_ARTIFACT_SIZE = 2 * 1024 * 1024;
|
|
3457
|
+
var MAX_ATTACHMENT_SIZE = 5 * 1024 * 1024;
|
|
3458
|
+
function base64ByteLength(base64) {
|
|
3459
|
+
const stripped = base64.replace(/^data:[^,]*,/, "");
|
|
3460
|
+
return Buffer.from(stripped, "base64").byteLength;
|
|
3461
|
+
}
|
|
3462
|
+
function sha256Hex(bytes) {
|
|
3463
|
+
return createHash4("sha256").update(bytes).digest("hex");
|
|
3464
|
+
}
|
|
3465
|
+
async function readFileForUpload(filePath, maxSize, kind) {
|
|
3466
|
+
const bytes = await readFile(filePath);
|
|
3467
|
+
if (bytes.byteLength === 0) {
|
|
3468
|
+
throw new Error(`File is empty: ${filePath}`);
|
|
3469
|
+
}
|
|
3470
|
+
if (bytes.byteLength > maxSize) {
|
|
3471
|
+
const mb = Math.round(maxSize / (1024 * 1024));
|
|
3472
|
+
throw new Error(`File is ${bytes.byteLength} bytes, over the ${maxSize}-byte (${mb}MB) ${kind} limit.`);
|
|
3473
|
+
}
|
|
3474
|
+
return bytes;
|
|
3475
|
+
}
|
|
3476
|
+
function requireExactlyOneScope(scope) {
|
|
3477
|
+
if ([scope.cardId, scope.planId, scope.workspaceId].filter(Boolean).length !== 1) {
|
|
3478
|
+
throw new Error("Provide exactly one of cardId, planId, or workspaceId.");
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
var SIGNED_UPLOAD_TIMEOUT_MS = 30000;
|
|
3482
|
+
async function putToSignedUrl(uploadUrl, bytes, contentType) {
|
|
3483
|
+
let res;
|
|
3484
|
+
try {
|
|
3485
|
+
res = await fetch(uploadUrl, {
|
|
3486
|
+
method: "PUT",
|
|
3487
|
+
headers: { "Content-Type": contentType },
|
|
3488
|
+
body: bytes,
|
|
3489
|
+
signal: AbortSignal.timeout(SIGNED_UPLOAD_TIMEOUT_MS)
|
|
3490
|
+
});
|
|
3491
|
+
} catch (err) {
|
|
3492
|
+
if (err instanceof DOMException && err.name === "TimeoutError") {
|
|
3493
|
+
throw new Error(`Direct storage upload timed out after ${SIGNED_UPLOAD_TIMEOUT_MS}ms.`);
|
|
3494
|
+
}
|
|
3495
|
+
throw err;
|
|
3496
|
+
}
|
|
3497
|
+
if (!res.ok) {
|
|
3498
|
+
const detail = await res.text().catch(() => "");
|
|
3499
|
+
throw new Error(`Direct storage upload failed: ${res.status}${detail ? ` — ${detail}` : ""}`);
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3408
3502
|
var memorySessions = new Map;
|
|
3409
3503
|
function parseLabelList(raw) {
|
|
3410
3504
|
if (raw === undefined || raw === null)
|
|
@@ -3865,7 +3959,7 @@ var TOOLS = {
|
|
|
3865
3959
|
}
|
|
3866
3960
|
},
|
|
3867
3961
|
harmony_upload_card_attachment: {
|
|
3868
|
-
description: "Upload a file attachment to a card (e.g. a pasted screenshot or a document). Provide the file either as `filePath` (a local path the MCP server can read — works in local/stdio mode) or as `base64Data` (raw base64 bytes — works everywhere, including remote mode). Max 5MB. Allowed: PNG, JPEG, GIF, WebP, HEIC/HEIF, PDF, DOC/DOCX, XLS/XLSX, TXT, CSV. Returns the stored attachment with a short-lived signed URL.",
|
|
3962
|
+
description: "Upload a file attachment to a card (e.g. a pasted screenshot or a document). Provide the file either as `filePath` (a local path the MCP server can read — works in local/stdio mode) or as `base64Data` (raw base64 bytes — works everywhere, including remote mode). With `filePath` the server uploads direct-to-storage (no base64 through the model context); `base64Data` is the small-file fallback. Max 5MB. Allowed: PNG, JPEG, GIF, WebP, HEIC/HEIF, PDF, DOC/DOCX, XLS/XLSX, TXT, CSV. Returns the stored attachment with a short-lived signed URL. For large files on the hosted MCP server (where only base64Data works), prefer the two-step harmony_request_card_attachment_upload_url + harmony_finalize_card_attachment handshake, which keeps bytes out of the model context.",
|
|
3869
3963
|
inputSchema: {
|
|
3870
3964
|
type: "object",
|
|
3871
3965
|
properties: {
|
|
@@ -3890,6 +3984,58 @@ var TOOLS = {
|
|
|
3890
3984
|
required: ["cardId"]
|
|
3891
3985
|
}
|
|
3892
3986
|
},
|
|
3987
|
+
harmony_request_card_attachment_upload_url: {
|
|
3988
|
+
description: "Step 1 of the agent-driven card-attachment upload — use this for large files or when running against the hosted MCP server (which cannot read your local disk). Mints a one-shot signed Supabase Storage upload URL for a server-chosen path under the card. Provide cardId, fileName (with extension), the byte size, and optionally fileType. Returns { uploadUrl, token, storagePath, fileType }. Next: PUT the raw bytes straight to uploadUrl (e.g. `curl -X PUT --data-binary @file.png '<uploadUrl>'`) — no bytes pass through the model context — then call harmony_finalize_card_attachment with the returned storagePath. Max 5MB; allowed: PNG, JPEG, GIF, WebP, HEIC/HEIF, PDF, DOC/DOCX, XLS/XLSX, TXT, CSV.",
|
|
3989
|
+
inputSchema: {
|
|
3990
|
+
type: "object",
|
|
3991
|
+
properties: {
|
|
3992
|
+
cardId: { type: "string", description: "Card UUID" },
|
|
3993
|
+
fileName: {
|
|
3994
|
+
type: "string",
|
|
3995
|
+
description: "File name including extension (e.g. 'screenshot.png')."
|
|
3996
|
+
},
|
|
3997
|
+
fileType: {
|
|
3998
|
+
type: "string",
|
|
3999
|
+
description: "Optional MIME type (e.g. 'image/png'). Inferred from the file extension when omitted."
|
|
4000
|
+
},
|
|
4001
|
+
size: {
|
|
4002
|
+
type: "number",
|
|
4003
|
+
description: "File size in bytes (rejected early if over 5MB)."
|
|
4004
|
+
}
|
|
4005
|
+
},
|
|
4006
|
+
required: ["cardId", "fileName", "size"]
|
|
4007
|
+
}
|
|
4008
|
+
},
|
|
4009
|
+
harmony_finalize_card_attachment: {
|
|
4010
|
+
description: "Step 2 of the agent-driven card-attachment upload. After PUTting the bytes to the signed uploadUrl from harmony_request_card_attachment_upload_url, call this with the returned storagePath to validate and register the attachment. The server re-downloads the object and enforces size, an allowlisted content-type (magic-byte sniff, never the declared type), and — when you pass sha256 — an integrity check, deleting the object and failing on any mismatch. Returns the stored attachment with a short-lived signed URL.",
|
|
4011
|
+
inputSchema: {
|
|
4012
|
+
type: "object",
|
|
4013
|
+
properties: {
|
|
4014
|
+
cardId: { type: "string", description: "Card UUID" },
|
|
4015
|
+
storagePath: {
|
|
4016
|
+
type: "string",
|
|
4017
|
+
description: "The storagePath returned by harmony_request_card_attachment_upload_url."
|
|
4018
|
+
},
|
|
4019
|
+
fileName: {
|
|
4020
|
+
type: "string",
|
|
4021
|
+
description: "File name including extension (e.g. 'screenshot.png')."
|
|
4022
|
+
},
|
|
4023
|
+
fileType: {
|
|
4024
|
+
type: "string",
|
|
4025
|
+
description: "Optional MIME type; inferred from the extension when omitted."
|
|
4026
|
+
},
|
|
4027
|
+
sha256: {
|
|
4028
|
+
type: "string",
|
|
4029
|
+
description: "Optional hex SHA-256 of the uploaded bytes; verified against the stored object."
|
|
4030
|
+
},
|
|
4031
|
+
size: {
|
|
4032
|
+
type: "number",
|
|
4033
|
+
description: "Optional byte size (advisory; re-validated server-side)."
|
|
4034
|
+
}
|
|
4035
|
+
},
|
|
4036
|
+
required: ["cardId", "storagePath", "fileName"]
|
|
4037
|
+
}
|
|
4038
|
+
},
|
|
3893
4039
|
harmony_classify_card: {
|
|
3894
4040
|
description: "Classify a card with the LLM classifier: sets `intent` (plan/think/implement/review), `complexity_score` (0-10), and `model_tier` (simple/advanced/research), stamps `classified_at`, and applies the canonical type label (feature/bug/idea). Use this right after creating a card (e.g. in the `hmy-new` flow) so it's classified in-flow instead of waiting for it to surface on the web board. Idempotent — safe to re-run. Never touches the user-owned `model_override`.",
|
|
3895
4041
|
inputSchema: {
|
|
@@ -3900,6 +4046,102 @@ var TOOLS = {
|
|
|
3900
4046
|
required: ["cardId"]
|
|
3901
4047
|
}
|
|
3902
4048
|
},
|
|
4049
|
+
harmony_upload_artifact: {
|
|
4050
|
+
description: "Host a self-contained HTML document (e.g. a visual design draft or diagram) in Harmony and link it to a card, a plan, or a workspace. The file is stored privately and rendered in-app inside a sandboxed cross-origin iframe. Provide exactly one of cardId, planId, or workspaceId. Supply the HTML as `filePath` (a local path the MCP server can read — uploaded direct-to-storage, no base64 through the model context) or `base64Data` (the small-file fallback). Only text/html, max 2MB. Returns the stored artifact with a short-lived signed URL; call harmony_share_artifact to mint a public link. For large files on the hosted MCP server (where only base64Data works), prefer the two-step harmony_request_artifact_upload_url + harmony_finalize_artifact handshake, which keeps bytes out of the model context.",
|
|
4051
|
+
inputSchema: {
|
|
4052
|
+
type: "object",
|
|
4053
|
+
properties: {
|
|
4054
|
+
title: {
|
|
4055
|
+
type: "string",
|
|
4056
|
+
description: "Display title (defaults to the file basename)."
|
|
4057
|
+
},
|
|
4058
|
+
cardId: { type: "string", description: "Link to this card (UUID)." },
|
|
4059
|
+
planId: { type: "string", description: "Link to this plan (UUID)." },
|
|
4060
|
+
workspaceId: {
|
|
4061
|
+
type: "string",
|
|
4062
|
+
description: "Attach to this workspace as a standalone artifact (UUID)."
|
|
4063
|
+
},
|
|
4064
|
+
filePath: {
|
|
4065
|
+
type: "string",
|
|
4066
|
+
description: "Absolute path to a local .html file the MCP server process can read. Mutually exclusive with base64Data."
|
|
4067
|
+
},
|
|
4068
|
+
base64Data: {
|
|
4069
|
+
type: "string",
|
|
4070
|
+
description: "Base64-encoded HTML bytes (a `data:` URL prefix is accepted and stripped). Mutually exclusive with filePath."
|
|
4071
|
+
}
|
|
4072
|
+
},
|
|
4073
|
+
required: []
|
|
4074
|
+
}
|
|
4075
|
+
},
|
|
4076
|
+
harmony_request_artifact_upload_url: {
|
|
4077
|
+
description: "Step 1 of the agent-driven artifact upload — use this for large files or when running against the hosted MCP server (which cannot read your local disk), instead of streaming base64 through the model context. Mints a one-shot signed Supabase Storage upload URL for a server-chosen path. Provide exactly one of cardId/planId/workspaceId, optionally a title and the byte size. Returns { uploadUrl, token, storagePath }. Next: PUT the raw HTML bytes straight to uploadUrl (e.g. `curl -X PUT --data-binary @doc.html '<uploadUrl>'`), then call harmony_finalize_artifact with the returned storagePath. Only text/html, max 2MB.",
|
|
4078
|
+
inputSchema: {
|
|
4079
|
+
type: "object",
|
|
4080
|
+
properties: {
|
|
4081
|
+
title: {
|
|
4082
|
+
type: "string",
|
|
4083
|
+
description: "Display title (defaults to the file basename at finalize)."
|
|
4084
|
+
},
|
|
4085
|
+
cardId: { type: "string", description: "Link to this card (UUID)." },
|
|
4086
|
+
planId: { type: "string", description: "Link to this plan (UUID)." },
|
|
4087
|
+
workspaceId: {
|
|
4088
|
+
type: "string",
|
|
4089
|
+
description: "Attach to this workspace as a standalone artifact (UUID)."
|
|
4090
|
+
},
|
|
4091
|
+
contentType: {
|
|
4092
|
+
type: "string",
|
|
4093
|
+
description: "MIME type; only 'text/html' is accepted (the default)."
|
|
4094
|
+
},
|
|
4095
|
+
size: {
|
|
4096
|
+
type: "number",
|
|
4097
|
+
description: "File size in bytes (rejected early if over 2MB)."
|
|
4098
|
+
}
|
|
4099
|
+
},
|
|
4100
|
+
required: []
|
|
4101
|
+
}
|
|
4102
|
+
},
|
|
4103
|
+
harmony_finalize_artifact: {
|
|
4104
|
+
description: "Step 2 of the agent-driven artifact upload. After PUTting the HTML bytes to the signed uploadUrl from harmony_request_artifact_upload_url, call this with the returned storagePath to validate and register the artifact. The server re-downloads the object and enforces size, the text/html content-type (magic-byte sniff), and — when you pass sha256 — an integrity check, deleting the object and failing on any mismatch. Provide the same one of cardId/planId/workspaceId used for the upload URL. Returns the stored artifact with a short-lived signed URL; call harmony_share_artifact to mint a public link.",
|
|
4105
|
+
inputSchema: {
|
|
4106
|
+
type: "object",
|
|
4107
|
+
properties: {
|
|
4108
|
+
storagePath: {
|
|
4109
|
+
type: "string",
|
|
4110
|
+
description: "The storagePath returned by harmony_request_artifact_upload_url."
|
|
4111
|
+
},
|
|
4112
|
+
title: { type: "string", description: "Display title." },
|
|
4113
|
+
cardId: { type: "string", description: "Link to this card (UUID)." },
|
|
4114
|
+
planId: { type: "string", description: "Link to this plan (UUID)." },
|
|
4115
|
+
workspaceId: {
|
|
4116
|
+
type: "string",
|
|
4117
|
+
description: "Attach to this workspace as a standalone artifact (UUID)."
|
|
4118
|
+
},
|
|
4119
|
+
sha256: {
|
|
4120
|
+
type: "string",
|
|
4121
|
+
description: "Optional hex SHA-256 of the uploaded bytes; verified against the stored object."
|
|
4122
|
+
},
|
|
4123
|
+
size: {
|
|
4124
|
+
type: "number",
|
|
4125
|
+
description: "Optional byte size (advisory; re-validated server-side)."
|
|
4126
|
+
}
|
|
4127
|
+
},
|
|
4128
|
+
required: ["storagePath"]
|
|
4129
|
+
}
|
|
4130
|
+
},
|
|
4131
|
+
harmony_share_artifact: {
|
|
4132
|
+
description: "Create a public, unauthenticated share link for a hosted artifact. Anyone with the link can view the rendered HTML without a Harmony account. Returns the share token and the full public URL.",
|
|
4133
|
+
inputSchema: {
|
|
4134
|
+
type: "object",
|
|
4135
|
+
properties: {
|
|
4136
|
+
artifactId: { type: "string", description: "Artifact UUID" },
|
|
4137
|
+
expiresInDays: {
|
|
4138
|
+
type: "number",
|
|
4139
|
+
description: "Optional expiry in days. Omit for a link that never expires."
|
|
4140
|
+
}
|
|
4141
|
+
},
|
|
4142
|
+
required: ["artifactId"]
|
|
4143
|
+
}
|
|
4144
|
+
},
|
|
3903
4145
|
harmony_get_card_external_links: {
|
|
3904
4146
|
description: "Get external URL references attached to a card (links to docs, gists, dashboards, etc.).",
|
|
3905
4147
|
inputSchema: {
|
|
@@ -4859,6 +5101,110 @@ var TOOLS = {
|
|
|
4859
5101
|
required: ["planId"]
|
|
4860
5102
|
}
|
|
4861
5103
|
},
|
|
5104
|
+
harmony_list_playbook: {
|
|
5105
|
+
description: "List a workspace's playbooks (reusable process definitions). Returns each playbook's name, version, steps_version, and state. Read-only.",
|
|
5106
|
+
inputSchema: {
|
|
5107
|
+
type: "object",
|
|
5108
|
+
properties: {
|
|
5109
|
+
workspaceId: {
|
|
5110
|
+
type: "string",
|
|
5111
|
+
description: "Workspace ID (optional if context set)"
|
|
5112
|
+
}
|
|
5113
|
+
},
|
|
5114
|
+
required: []
|
|
5115
|
+
}
|
|
5116
|
+
},
|
|
5117
|
+
harmony_get_playbook: {
|
|
5118
|
+
description: "Get one playbook by ID, including its steps/stages definition and its recent runs. Read-only.",
|
|
5119
|
+
inputSchema: {
|
|
5120
|
+
type: "object",
|
|
5121
|
+
properties: {
|
|
5122
|
+
playbookId: { type: "string", description: "Playbook ID (UUID)" }
|
|
5123
|
+
},
|
|
5124
|
+
required: ["playbookId"]
|
|
5125
|
+
}
|
|
5126
|
+
},
|
|
5127
|
+
harmony_run_playbook: {
|
|
5128
|
+
description: "Run a playbook server-side and return the finalized run. Only legacy automation playbooks (steps_version 1) are runnable; stage playbooks (steps_version 2) are rejected. The server drives every step to completion.",
|
|
5129
|
+
inputSchema: {
|
|
5130
|
+
type: "object",
|
|
5131
|
+
properties: {
|
|
5132
|
+
playbookId: {
|
|
5133
|
+
type: "string",
|
|
5134
|
+
description: "Playbook ID to run (UUID)"
|
|
5135
|
+
}
|
|
5136
|
+
},
|
|
5137
|
+
required: ["playbookId"]
|
|
5138
|
+
}
|
|
5139
|
+
},
|
|
5140
|
+
harmony_create_playbook: {
|
|
5141
|
+
description: "Create a new playbook in a workspace. Default steps_version 1 is a legacy automation macro (an array of tool steps); steps_version 2 is the Method stage model (an array of stage objects).",
|
|
5142
|
+
inputSchema: {
|
|
5143
|
+
type: "object",
|
|
5144
|
+
properties: {
|
|
5145
|
+
workspaceId: {
|
|
5146
|
+
type: "string",
|
|
5147
|
+
description: "Workspace ID (optional if context set)"
|
|
5148
|
+
},
|
|
5149
|
+
name: { type: "string", description: "Playbook name" },
|
|
5150
|
+
description: { type: "string", description: "Playbook description" },
|
|
5151
|
+
stepsVersion: {
|
|
5152
|
+
type: "number",
|
|
5153
|
+
enum: [1, 2],
|
|
5154
|
+
description: "1 = automation macro (tool steps), 2 = Method stage model (stage objects). Default 1."
|
|
5155
|
+
},
|
|
5156
|
+
steps: {
|
|
5157
|
+
type: "array",
|
|
5158
|
+
description: "Steps (steps_version 1: tool-step objects) or stages (steps_version 2: stage objects).",
|
|
5159
|
+
items: { type: "object" }
|
|
5160
|
+
}
|
|
5161
|
+
},
|
|
5162
|
+
required: ["name"]
|
|
5163
|
+
}
|
|
5164
|
+
},
|
|
5165
|
+
harmony_update_playbook: {
|
|
5166
|
+
description: "Update a playbook's name, description, steps/stages, enabled flag, or lifecycle state ('active'|'deprecated').",
|
|
5167
|
+
inputSchema: {
|
|
5168
|
+
type: "object",
|
|
5169
|
+
properties: {
|
|
5170
|
+
playbookId: {
|
|
5171
|
+
type: "string",
|
|
5172
|
+
description: "Playbook ID to update (UUID)"
|
|
5173
|
+
},
|
|
5174
|
+
name: { type: "string", description: "New name" },
|
|
5175
|
+
description: { type: "string", description: "New description" },
|
|
5176
|
+
steps: {
|
|
5177
|
+
type: "array",
|
|
5178
|
+
description: "New steps (v1) or stages (v2) array.",
|
|
5179
|
+
items: { type: "object" }
|
|
5180
|
+
},
|
|
5181
|
+
enabled: {
|
|
5182
|
+
type: "boolean",
|
|
5183
|
+
description: "Enable/disable the playbook"
|
|
5184
|
+
},
|
|
5185
|
+
state: {
|
|
5186
|
+
type: "string",
|
|
5187
|
+
enum: ["active", "deprecated"],
|
|
5188
|
+
description: "Lifecycle state"
|
|
5189
|
+
}
|
|
5190
|
+
},
|
|
5191
|
+
required: ["playbookId"]
|
|
5192
|
+
}
|
|
5193
|
+
},
|
|
5194
|
+
harmony_save_card_as_playbook: {
|
|
5195
|
+
description: "Save an existing card as a new steps_version 1 (automation) playbook, seeding one create-card step from the card. Returns the created playbook.",
|
|
5196
|
+
inputSchema: {
|
|
5197
|
+
type: "object",
|
|
5198
|
+
properties: {
|
|
5199
|
+
cardId: { type: "string", description: "Card ID to template (UUID)" },
|
|
5200
|
+
name: {
|
|
5201
|
+
type: "string",
|
|
5202
|
+
description: "Name for the new playbook (defaults to the card title)"
|
|
5203
|
+
}
|
|
5204
|
+
},
|
|
5205
|
+
required: ["cardId"]
|
|
5206
|
+
}
|
|
5207
|
+
},
|
|
4862
5208
|
harmony_signup: {
|
|
4863
5209
|
description: "Create a new user account. Returns a JWT session for subsequent authenticated calls. No API key required.",
|
|
4864
5210
|
inputSchema: {
|
|
@@ -5379,32 +5725,163 @@ async function handleToolCall(name, args, deps) {
|
|
|
5379
5725
|
const cardId = z.string().uuid().parse(args.cardId);
|
|
5380
5726
|
const filePath = args.filePath != null ? z.string().parse(args.filePath) : undefined;
|
|
5381
5727
|
const base64Data = args.base64Data != null ? z.string().parse(args.base64Data) : undefined;
|
|
5382
|
-
|
|
5728
|
+
const fileName = args.fileName != null ? z.string().parse(args.fileName) : undefined;
|
|
5383
5729
|
const contentType = args.contentType != null ? z.string().parse(args.contentType) : undefined;
|
|
5384
5730
|
if (filePath && base64Data) {
|
|
5385
5731
|
throw new Error("Provide either filePath or base64Data, not both.");
|
|
5386
5732
|
}
|
|
5387
|
-
let data;
|
|
5388
5733
|
if (filePath) {
|
|
5389
|
-
const bytes = await
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5734
|
+
const bytes = await readFileForUpload(filePath, MAX_ATTACHMENT_SIZE, "attachment");
|
|
5735
|
+
const resolvedName = fileName || basename(filePath);
|
|
5736
|
+
const signed = await client3.requestCardAttachmentUploadUrl(cardId, {
|
|
5737
|
+
fileName: resolvedName,
|
|
5738
|
+
fileType: contentType,
|
|
5739
|
+
size: bytes.byteLength
|
|
5740
|
+
});
|
|
5741
|
+
await putToSignedUrl(signed.uploadUrl, bytes, contentType || signed.fileType || "application/octet-stream");
|
|
5742
|
+
return await client3.finalizeCardAttachment(cardId, {
|
|
5743
|
+
storagePath: signed.storagePath,
|
|
5744
|
+
fileName: resolvedName,
|
|
5745
|
+
fileType: contentType || signed.fileType,
|
|
5746
|
+
sha256: sha256Hex(bytes),
|
|
5747
|
+
size: bytes.byteLength
|
|
5748
|
+
});
|
|
5749
|
+
}
|
|
5750
|
+
if (base64Data) {
|
|
5396
5751
|
if (!fileName) {
|
|
5397
5752
|
throw new Error("fileName is required when using base64Data.");
|
|
5398
5753
|
}
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5754
|
+
if (base64ByteLength(base64Data) > MAX_ATTACHMENT_SIZE) {
|
|
5755
|
+
throw new Error(`File is over the 5MB attachment limit. Use the harmony_request_card_attachment_upload_url + harmony_finalize_card_attachment handshake for large files.`);
|
|
5756
|
+
}
|
|
5757
|
+
return await client3.uploadCardAttachment(cardId, {
|
|
5758
|
+
fileName,
|
|
5759
|
+
data: base64Data,
|
|
5760
|
+
fileType: contentType
|
|
5761
|
+
});
|
|
5762
|
+
}
|
|
5763
|
+
throw new Error("Provide either filePath or base64Data.");
|
|
5764
|
+
}
|
|
5765
|
+
case "harmony_upload_artifact": {
|
|
5766
|
+
const title = args.title != null ? z.string().parse(args.title) : undefined;
|
|
5767
|
+
const cardId = args.cardId != null ? z.string().uuid().parse(args.cardId) : undefined;
|
|
5768
|
+
const planId = args.planId != null ? z.string().uuid().parse(args.planId) : undefined;
|
|
5769
|
+
const workspaceId = args.workspaceId != null ? z.string().uuid().parse(args.workspaceId) : undefined;
|
|
5770
|
+
requireExactlyOneScope({ cardId, planId, workspaceId });
|
|
5771
|
+
const filePath = args.filePath != null ? z.string().parse(args.filePath) : undefined;
|
|
5772
|
+
const base64Data = args.base64Data != null ? z.string().parse(args.base64Data) : undefined;
|
|
5773
|
+
if (filePath && base64Data) {
|
|
5774
|
+
throw new Error("Provide either filePath or base64Data, not both.");
|
|
5775
|
+
}
|
|
5776
|
+
if (filePath) {
|
|
5777
|
+
const bytes = await readFileForUpload(filePath, MAX_ARTIFACT_SIZE, "artifact");
|
|
5778
|
+
const resolvedTitle = title || basename(filePath);
|
|
5779
|
+
const signed = await client3.requestArtifactUploadUrl({
|
|
5780
|
+
title: resolvedTitle,
|
|
5781
|
+
cardId,
|
|
5782
|
+
planId,
|
|
5783
|
+
workspaceId,
|
|
5784
|
+
contentType: "text/html",
|
|
5785
|
+
size: bytes.byteLength
|
|
5786
|
+
});
|
|
5787
|
+
await putToSignedUrl(signed.uploadUrl, bytes, "text/html");
|
|
5788
|
+
return await client3.finalizeArtifact({
|
|
5789
|
+
storagePath: signed.storagePath,
|
|
5790
|
+
sha256: sha256Hex(bytes),
|
|
5791
|
+
size: bytes.byteLength,
|
|
5792
|
+
title: resolvedTitle,
|
|
5793
|
+
cardId,
|
|
5794
|
+
planId,
|
|
5795
|
+
workspaceId
|
|
5796
|
+
});
|
|
5402
5797
|
}
|
|
5403
|
-
|
|
5798
|
+
if (base64Data) {
|
|
5799
|
+
if (base64ByteLength(base64Data) > MAX_ARTIFACT_SIZE) {
|
|
5800
|
+
throw new Error(`Artifact is over the 2MB limit. Use the harmony_request_artifact_upload_url + harmony_finalize_artifact handshake for large files.`);
|
|
5801
|
+
}
|
|
5802
|
+
return await client3.uploadArtifact({
|
|
5803
|
+
title,
|
|
5804
|
+
cardId,
|
|
5805
|
+
planId,
|
|
5806
|
+
workspaceId,
|
|
5807
|
+
data: base64Data
|
|
5808
|
+
});
|
|
5809
|
+
}
|
|
5810
|
+
throw new Error("Provide either filePath or base64Data.");
|
|
5811
|
+
}
|
|
5812
|
+
case "harmony_request_artifact_upload_url": {
|
|
5813
|
+
const title = args.title != null ? z.string().parse(args.title) : undefined;
|
|
5814
|
+
const cardId = args.cardId != null ? z.string().uuid().parse(args.cardId) : undefined;
|
|
5815
|
+
const planId = args.planId != null ? z.string().uuid().parse(args.planId) : undefined;
|
|
5816
|
+
const workspaceId = args.workspaceId != null ? z.string().uuid().parse(args.workspaceId) : undefined;
|
|
5817
|
+
requireExactlyOneScope({ cardId, planId, workspaceId });
|
|
5818
|
+
const contentType = args.contentType != null ? z.string().parse(args.contentType) : undefined;
|
|
5819
|
+
const size = args.size != null ? z.number().positive().parse(args.size) : undefined;
|
|
5820
|
+
if (size != null && size > MAX_ARTIFACT_SIZE) {
|
|
5821
|
+
throw new Error(`Declared size ${size} bytes is over the ${MAX_ARTIFACT_SIZE}-byte (2MB) artifact limit.`);
|
|
5822
|
+
}
|
|
5823
|
+
return await client3.requestArtifactUploadUrl({
|
|
5824
|
+
title,
|
|
5825
|
+
cardId,
|
|
5826
|
+
planId,
|
|
5827
|
+
workspaceId,
|
|
5828
|
+
contentType,
|
|
5829
|
+
size
|
|
5830
|
+
});
|
|
5831
|
+
}
|
|
5832
|
+
case "harmony_finalize_artifact": {
|
|
5833
|
+
const storagePath = z.string().parse(args.storagePath);
|
|
5834
|
+
const title = args.title != null ? z.string().parse(args.title) : undefined;
|
|
5835
|
+
const cardId = args.cardId != null ? z.string().uuid().parse(args.cardId) : undefined;
|
|
5836
|
+
const planId = args.planId != null ? z.string().uuid().parse(args.planId) : undefined;
|
|
5837
|
+
const workspaceId = args.workspaceId != null ? z.string().uuid().parse(args.workspaceId) : undefined;
|
|
5838
|
+
requireExactlyOneScope({ cardId, planId, workspaceId });
|
|
5839
|
+
const sha256 = args.sha256 != null ? z.string().parse(args.sha256) : undefined;
|
|
5840
|
+
const size = args.size != null ? z.number().positive().parse(args.size) : undefined;
|
|
5841
|
+
return await client3.finalizeArtifact({
|
|
5842
|
+
storagePath,
|
|
5843
|
+
sha256,
|
|
5844
|
+
size,
|
|
5845
|
+
title,
|
|
5846
|
+
cardId,
|
|
5847
|
+
planId,
|
|
5848
|
+
workspaceId
|
|
5849
|
+
});
|
|
5850
|
+
}
|
|
5851
|
+
case "harmony_request_card_attachment_upload_url": {
|
|
5852
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
5853
|
+
const fileName = z.string().parse(args.fileName);
|
|
5854
|
+
const fileType = args.fileType != null ? z.string().parse(args.fileType) : undefined;
|
|
5855
|
+
const size = z.number().positive().parse(args.size);
|
|
5856
|
+
if (size > MAX_ATTACHMENT_SIZE) {
|
|
5857
|
+
throw new Error(`Declared size ${size} bytes is over the ${MAX_ATTACHMENT_SIZE}-byte (5MB) attachment limit.`);
|
|
5858
|
+
}
|
|
5859
|
+
return await client3.requestCardAttachmentUploadUrl(cardId, {
|
|
5404
5860
|
fileName,
|
|
5405
|
-
|
|
5406
|
-
|
|
5861
|
+
fileType,
|
|
5862
|
+
size
|
|
5407
5863
|
});
|
|
5864
|
+
}
|
|
5865
|
+
case "harmony_finalize_card_attachment": {
|
|
5866
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
5867
|
+
const storagePath = z.string().parse(args.storagePath);
|
|
5868
|
+
const fileName = z.string().parse(args.fileName);
|
|
5869
|
+
const fileType = args.fileType != null ? z.string().parse(args.fileType) : undefined;
|
|
5870
|
+
const sha256 = args.sha256 != null ? z.string().parse(args.sha256) : undefined;
|
|
5871
|
+
const size = args.size != null ? z.number().positive().parse(args.size) : undefined;
|
|
5872
|
+
return await client3.finalizeCardAttachment(cardId, {
|
|
5873
|
+
storagePath,
|
|
5874
|
+
fileName,
|
|
5875
|
+
fileType,
|
|
5876
|
+
sha256,
|
|
5877
|
+
size
|
|
5878
|
+
});
|
|
5879
|
+
}
|
|
5880
|
+
case "harmony_share_artifact": {
|
|
5881
|
+
const artifactId = z.string().uuid().parse(args.artifactId);
|
|
5882
|
+
const expiresInDays = args.expiresInDays != null ? z.number().positive().parse(args.expiresInDays) : undefined;
|
|
5883
|
+
const expiresAt = expiresInDays ? new Date(Date.now() + expiresInDays * 86400000).toISOString() : undefined;
|
|
5884
|
+
const result = await client3.createArtifactShareLink(artifactId, expiresAt);
|
|
5408
5885
|
return result;
|
|
5409
5886
|
}
|
|
5410
5887
|
case "harmony_get_card_external_links": {
|
|
@@ -6310,6 +6787,57 @@ async function handleToolCall(name, args, deps) {
|
|
|
6310
6787
|
...results
|
|
6311
6788
|
};
|
|
6312
6789
|
}
|
|
6790
|
+
case "harmony_list_playbook": {
|
|
6791
|
+
const workspaceId = args.workspaceId || getWorkspaceId();
|
|
6792
|
+
const result = await client3.listPlaybooks(workspaceId);
|
|
6793
|
+
return {
|
|
6794
|
+
success: true,
|
|
6795
|
+
playbooks: result.playbooks,
|
|
6796
|
+
count: result.playbooks.length
|
|
6797
|
+
};
|
|
6798
|
+
}
|
|
6799
|
+
case "harmony_get_playbook": {
|
|
6800
|
+
const playbookId = z.string().uuid().parse(args.playbookId);
|
|
6801
|
+
const result = await client3.getPlaybook(playbookId);
|
|
6802
|
+
return { success: true, playbook: result.playbook, runs: result.runs };
|
|
6803
|
+
}
|
|
6804
|
+
case "harmony_run_playbook": {
|
|
6805
|
+
const playbookId = z.string().uuid().parse(args.playbookId);
|
|
6806
|
+
const result = await client3.runPlaybook(playbookId);
|
|
6807
|
+
return { success: true, run: result.run };
|
|
6808
|
+
}
|
|
6809
|
+
case "harmony_create_playbook": {
|
|
6810
|
+
const workspaceId = args.workspaceId || getWorkspaceId();
|
|
6811
|
+
const name2 = z.string().min(1).max(200).parse(args.name);
|
|
6812
|
+
const stepsVersion = args.stepsVersion !== undefined ? z.union([z.literal(1), z.literal(2)]).parse(args.stepsVersion) : undefined;
|
|
6813
|
+
const result = await client3.createPlaybook({
|
|
6814
|
+
workspaceId,
|
|
6815
|
+
name: name2,
|
|
6816
|
+
description: args.description,
|
|
6817
|
+
steps: args.steps,
|
|
6818
|
+
stepsVersion
|
|
6819
|
+
});
|
|
6820
|
+
return { success: true, playbook: result.playbook };
|
|
6821
|
+
}
|
|
6822
|
+
case "harmony_update_playbook": {
|
|
6823
|
+
const playbookId = z.string().uuid().parse(args.playbookId);
|
|
6824
|
+
const result = await client3.updatePlaybook(playbookId, {
|
|
6825
|
+
name: args.name,
|
|
6826
|
+
description: args.description,
|
|
6827
|
+
steps: args.steps,
|
|
6828
|
+
enabled: args.enabled,
|
|
6829
|
+
state: args.state
|
|
6830
|
+
});
|
|
6831
|
+
return { success: true, playbook: result.playbook };
|
|
6832
|
+
}
|
|
6833
|
+
case "harmony_save_card_as_playbook": {
|
|
6834
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
6835
|
+
const result = await client3.savePlaybookFromCard({
|
|
6836
|
+
cardId,
|
|
6837
|
+
name: args.name
|
|
6838
|
+
});
|
|
6839
|
+
return { success: true, playbook: result.playbook };
|
|
6840
|
+
}
|
|
6313
6841
|
case "harmony_signup": {
|
|
6314
6842
|
const email = z.string().email().max(254).parse(args.email);
|
|
6315
6843
|
const password = z.string().min(8).max(128).parse(args.password);
|