@gethmy/mcp 3.7.0 → 3.9.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/README.md +3 -3
- package/dist/cli.js +623 -157
- package/dist/index.js +228 -97
- package/dist/lib/api-client.js +181 -16
- package/dist/lib/config.js +110 -14
- package/dist/lib/oauth-refresh.js +110 -14
- package/dist/run-hook-cli.js +54 -0
- package/package.json +2 -2
- package/src/api-client.ts +91 -1
- package/src/config.ts +262 -14
- package/src/prompt-builder.ts +1 -1
- package/src/server.ts +70 -4
- package/src/skills.ts +6 -80
- package/src/tui/agent-instructions.ts +335 -0
- package/src/tui/setup.ts +144 -63
- package/src/tui/writer.ts +118 -2
package/dist/index.js
CHANGED
|
@@ -18,8 +18,9 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/config.ts
|
|
21
|
+
import { execFileSync } from "node:child_process";
|
|
21
22
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
22
|
-
import { homedir } from "node:os";
|
|
23
|
+
import { homedir, tmpdir } from "node:os";
|
|
23
24
|
import { dirname, join as join2, parse, resolve } from "node:path";
|
|
24
25
|
function noteLegacyConfigDir(path) {
|
|
25
26
|
if (warnedLegacyConfigDir)
|
|
@@ -31,11 +32,28 @@ function noteLegacyLocalPin(path) {
|
|
|
31
32
|
if (warnedLegacyLocalPin)
|
|
32
33
|
return;
|
|
33
34
|
warnedLegacyLocalPin = true;
|
|
34
|
-
console.error(`Harmony: this repo is pinned by ${path}, the pre-#1082 name. ` + `
|
|
35
|
+
console.error(`Harmony: this repo is pinned by ${path}, the pre-#1082 name. ` + `Run \`harmony-agent doctor --fix\` in this repo to write ` + `${LOCAL_CONFIG_FILENAME}, or rename the file yourself. ` + `The fallback that finds it is temporary.`);
|
|
35
36
|
}
|
|
36
37
|
function noteLocalPinRename(from, to) {
|
|
37
38
|
console.error(`Harmony: wrote the repo pin to ${to} (the pre-#1082 ${from} is now ignored and can be deleted).`);
|
|
38
39
|
}
|
|
40
|
+
function noteUntrackedLocalPin(path) {
|
|
41
|
+
if (warnedUntrackedLocalPin)
|
|
42
|
+
return;
|
|
43
|
+
warnedUntrackedLocalPin = true;
|
|
44
|
+
console.error(`Harmony: ${path} is ignored by git, so it will not travel with a branch — ` + `a fresh clone, and every worktree the agent daemon cuts, will not have it. ` + `Commit it if you want it to describe this repo everywhere.`);
|
|
45
|
+
}
|
|
46
|
+
function isGitIgnored(path) {
|
|
47
|
+
try {
|
|
48
|
+
execFileSync("git", ["check-ignore", "--quiet", path], {
|
|
49
|
+
cwd: dirname(path),
|
|
50
|
+
stdio: "ignore"
|
|
51
|
+
});
|
|
52
|
+
return true;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
39
57
|
function getHmyRootDir() {
|
|
40
58
|
return join2(homedir(), CONFIG_DIR_NAME);
|
|
41
59
|
}
|
|
@@ -150,19 +168,96 @@ function saveLocalConfig(config, cwd) {
|
|
|
150
168
|
if (foundPath !== null && foundPath !== localConfigPath) {
|
|
151
169
|
noteLocalPinRename(foundPath, localConfigPath);
|
|
152
170
|
}
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (
|
|
162
|
-
|
|
163
|
-
|
|
171
|
+
const existing = readRawLocalConfig(foundPath ?? localConfigPath);
|
|
172
|
+
const merged = { ...existing };
|
|
173
|
+
if ("workspaceId" in config) {
|
|
174
|
+
if (config.workspaceId)
|
|
175
|
+
merged.workspaceId = config.workspaceId;
|
|
176
|
+
else
|
|
177
|
+
delete merged.workspaceId;
|
|
178
|
+
}
|
|
179
|
+
if ("projectId" in config) {
|
|
180
|
+
if (config.projectId)
|
|
181
|
+
merged.projectId = config.projectId;
|
|
182
|
+
else
|
|
183
|
+
delete merged.projectId;
|
|
184
|
+
}
|
|
185
|
+
writeFileSync2(localConfigPath, `${JSON.stringify(merged, null, localIndent(localConfigPath))}
|
|
186
|
+
`);
|
|
187
|
+
if (isGitIgnored(localConfigPath))
|
|
188
|
+
noteUntrackedLocalPin(localConfigPath);
|
|
164
189
|
return localConfigPath;
|
|
165
190
|
}
|
|
191
|
+
function readRawLocalConfig(path) {
|
|
192
|
+
let text;
|
|
193
|
+
try {
|
|
194
|
+
text = readFileSync2(path, "utf-8");
|
|
195
|
+
} catch {
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
const parsed = JSON.parse(text);
|
|
200
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
201
|
+
return parsed;
|
|
202
|
+
}
|
|
203
|
+
} catch {}
|
|
204
|
+
noteUnparsableLocalPin(path, text);
|
|
205
|
+
return {};
|
|
206
|
+
}
|
|
207
|
+
function noteUnparsableLocalPin(path, contents) {
|
|
208
|
+
let backup = null;
|
|
209
|
+
try {
|
|
210
|
+
backup = join2(tmpdir(), `hmy-pin-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json.bak`);
|
|
211
|
+
writeFileSync2(backup, contents);
|
|
212
|
+
} catch {
|
|
213
|
+
backup = null;
|
|
214
|
+
}
|
|
215
|
+
console.error(`Harmony: ${path} could not be parsed as JSON, so the pin write REPLACED it. ` + (backup ? `The previous contents are in ${backup}.` : "The previous contents could not be backed up and are gone.") + ` If it carried a "commands" block, re-add it.`);
|
|
216
|
+
}
|
|
217
|
+
function localIndent(configPath) {
|
|
218
|
+
const root = dirname(configPath);
|
|
219
|
+
for (const name of ["biome.json", "biome.jsonc"]) {
|
|
220
|
+
const parsed = readJsonish(join2(root, name));
|
|
221
|
+
if (!parsed || typeof parsed !== "object")
|
|
222
|
+
continue;
|
|
223
|
+
const formatter = parsed.formatter;
|
|
224
|
+
if (formatter?.indentStyle === "space") {
|
|
225
|
+
const width = formatter.indentWidth;
|
|
226
|
+
return typeof width === "number" && width > 0 ? width : 2;
|
|
227
|
+
}
|
|
228
|
+
return "\t";
|
|
229
|
+
}
|
|
230
|
+
const editorconfig = readTextSafely(join2(root, ".editorconfig"));
|
|
231
|
+
if (editorconfig) {
|
|
232
|
+
if (/^\s*indent_style\s*=\s*tab\s*$/im.test(editorconfig))
|
|
233
|
+
return "\t";
|
|
234
|
+
const size = editorconfig.match(/^\s*indent_size\s*=\s*(\d+)\s*$/im);
|
|
235
|
+
if (size) {
|
|
236
|
+
const width = Number(size[1]);
|
|
237
|
+
if (width > 0)
|
|
238
|
+
return width;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return 2;
|
|
242
|
+
}
|
|
243
|
+
function readJsonish(path) {
|
|
244
|
+
const text = readTextSafely(path);
|
|
245
|
+
if (text === null)
|
|
246
|
+
return null;
|
|
247
|
+
try {
|
|
248
|
+
const stripped = text.replace(/^\s*\/\/.*$/gm, "").replace(/,(\s*[}\]])/g, "$1");
|
|
249
|
+
return JSON.parse(stripped);
|
|
250
|
+
} catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function readTextSafely(path) {
|
|
255
|
+
try {
|
|
256
|
+
return readFileSync2(path, "utf-8");
|
|
257
|
+
} catch {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
166
261
|
function hasLocalConfig(cwd) {
|
|
167
262
|
return findLocalConfigPath(cwd) !== null;
|
|
168
263
|
}
|
|
@@ -296,7 +391,7 @@ function getMemoryDir() {
|
|
|
296
391
|
return config.memoryDir;
|
|
297
392
|
return join2(homedir(), ".harmony", "memory");
|
|
298
393
|
}
|
|
299
|
-
var DEFAULT_API_URL = "https://app.gethmy.com/api", LOCAL_CONFIG_FILENAME = ".hmy.json", LEGACY_LOCAL_CONFIG_FILENAME = ".harmony-mcp.json", CONFIG_DIR_NAME = ".hmy", CONFIG_DIR_SUBDIR = "agent", LEGACY_CONFIG_DIR_NAME = ".harmony-mcp", warnedLegacyConfigDir = false, warnedLegacyLocalPin = false;
|
|
394
|
+
var DEFAULT_API_URL = "https://app.gethmy.com/api", LOCAL_CONFIG_FILENAME = ".hmy.json", LEGACY_LOCAL_CONFIG_FILENAME = ".harmony-mcp.json", CONFIG_DIR_NAME = ".hmy", CONFIG_DIR_SUBDIR = "agent", LEGACY_CONFIG_DIR_NAME = ".harmony-mcp", warnedLegacyConfigDir = false, warnedLegacyLocalPin = false, warnedUntrackedLocalPin = false;
|
|
300
395
|
var init_config = () => {};
|
|
301
396
|
|
|
302
397
|
// src/prompt-builder.ts
|
|
@@ -726,7 +821,7 @@ var init_prompt_builder = __esm(() => {
|
|
|
726
821
|
};
|
|
727
822
|
VARIANT_INSTRUCTIONS = {
|
|
728
823
|
analysis: `ANALYSIS MODE: Analyze this task thoroughly. Identify requirements, constraints, edge cases, and potential challenges. Do NOT implement anything yet - focus on understanding and planning.`,
|
|
729
|
-
draft: `DRAFT MODE:
|
|
824
|
+
draft: `DRAFT MODE: Draft the approach for review before implementing. Cover the key decisions with their reasons, the data model and the API contracts, and success criteria a test can check. A short signature or schema sketch is fine wherever an interpretation gap would otherwise remain; function bodies, control flow and test code are not - an implementer transcribes a plan faithfully, defects included.`,
|
|
730
825
|
execute: `EXECUTE MODE: Implement this task completely. Write production-ready code following best practices. Include necessary tests and documentation.`
|
|
731
826
|
};
|
|
732
827
|
});
|
|
@@ -1037,7 +1132,7 @@ __export(exports_run_state, {
|
|
|
1037
1132
|
RUN_STATE_DIR_ENV: () => RUN_STATE_DIR_ENV,
|
|
1038
1133
|
MAX_POINTER_AGE_MS: () => MAX_POINTER_AGE_MS
|
|
1039
1134
|
});
|
|
1040
|
-
import { execFileSync } from "node:child_process";
|
|
1135
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1041
1136
|
import {
|
|
1042
1137
|
existsSync as existsSync3,
|
|
1043
1138
|
mkdirSync as mkdirSync3,
|
|
@@ -1094,7 +1189,7 @@ function psParentTable() {
|
|
|
1094
1189
|
return psTableCache;
|
|
1095
1190
|
const table = new Map;
|
|
1096
1191
|
try {
|
|
1097
|
-
const out =
|
|
1192
|
+
const out = execFileSync2("ps", ["-Ao", "pid=,ppid="], {
|
|
1098
1193
|
encoding: "utf-8",
|
|
1099
1194
|
timeout: 2000,
|
|
1100
1195
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1987,6 +2082,60 @@ var REVIEW_DISALLOWED_TOOLS = [
|
|
|
1987
2082
|
"mcp__harmony__harmony_delete_subtask",
|
|
1988
2083
|
"mcp__harmony__harmony_toggle_subtask"
|
|
1989
2084
|
];
|
|
2085
|
+
// ../harmony-shared/dist/runEventSanitize.js
|
|
2086
|
+
var REPLACEMENT = "�";
|
|
2087
|
+
function sanitizeRunEventString(value) {
|
|
2088
|
+
let out = "";
|
|
2089
|
+
for (let i = 0;i < value.length; i++) {
|
|
2090
|
+
const code = value.charCodeAt(i);
|
|
2091
|
+
if (code === 0)
|
|
2092
|
+
continue;
|
|
2093
|
+
if (code >= 55296 && code <= 56319) {
|
|
2094
|
+
const next = value.charCodeAt(i + 1);
|
|
2095
|
+
if (next >= 56320 && next <= 57343) {
|
|
2096
|
+
out += value[i] + value[i + 1];
|
|
2097
|
+
i++;
|
|
2098
|
+
continue;
|
|
2099
|
+
}
|
|
2100
|
+
out += REPLACEMENT;
|
|
2101
|
+
continue;
|
|
2102
|
+
}
|
|
2103
|
+
if (code >= 56320 && code <= 57343) {
|
|
2104
|
+
out += REPLACEMENT;
|
|
2105
|
+
continue;
|
|
2106
|
+
}
|
|
2107
|
+
out += value[i];
|
|
2108
|
+
}
|
|
2109
|
+
return out;
|
|
2110
|
+
}
|
|
2111
|
+
function sanitizeRunEventPayload(payload) {
|
|
2112
|
+
return walk(payload, new Map);
|
|
2113
|
+
}
|
|
2114
|
+
function sanitizeRunEventDraft(draft) {
|
|
2115
|
+
return { ...draft, payload: sanitizeRunEventPayload(draft.payload) };
|
|
2116
|
+
}
|
|
2117
|
+
function walk(value, seen) {
|
|
2118
|
+
if (typeof value === "string")
|
|
2119
|
+
return sanitizeRunEventString(value);
|
|
2120
|
+
if (value === null || typeof value !== "object")
|
|
2121
|
+
return value;
|
|
2122
|
+
const already = seen.get(value);
|
|
2123
|
+
if (already !== undefined)
|
|
2124
|
+
return already;
|
|
2125
|
+
if (Array.isArray(value)) {
|
|
2126
|
+
const out2 = [];
|
|
2127
|
+
seen.set(value, out2);
|
|
2128
|
+
for (const entry of value)
|
|
2129
|
+
out2.push(walk(entry, seen));
|
|
2130
|
+
return out2;
|
|
2131
|
+
}
|
|
2132
|
+
const out = {};
|
|
2133
|
+
seen.set(value, out);
|
|
2134
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
2135
|
+
out[sanitizeRunEventString(key)] = walk(entry, seen);
|
|
2136
|
+
}
|
|
2137
|
+
return out;
|
|
2138
|
+
}
|
|
1990
2139
|
// ../harmony-shared/dist/runRedaction.js
|
|
1991
2140
|
var MAX_INPUT_CHARS = 2000;
|
|
1992
2141
|
var MAX_OUTPUT_CHARS = 4000;
|
|
@@ -2507,6 +2656,15 @@ class HarmonyApiClient {
|
|
|
2507
2656
|
async registerWorkspaceAgent(workspaceId, data) {
|
|
2508
2657
|
return this.request("POST", `/workspaces/${workspaceId}/agents`, data);
|
|
2509
2658
|
}
|
|
2659
|
+
async reportAgentConfig(workspaceId, agentId, config) {
|
|
2660
|
+
return this.request("POST", `/workspaces/${workspaceId}/agents/${agentId}/reported-config`, { config });
|
|
2661
|
+
}
|
|
2662
|
+
async getWorkspaceModelConfig(workspaceId) {
|
|
2663
|
+
return this.request("GET", `/workspaces/${workspaceId}/model-config`);
|
|
2664
|
+
}
|
|
2665
|
+
async getModelCatalog() {
|
|
2666
|
+
return this.request("GET", "/model-catalog");
|
|
2667
|
+
}
|
|
2510
2668
|
async listProjects(workspaceId) {
|
|
2511
2669
|
return this.request("GET", `/workspaces/${workspaceId}/projects`);
|
|
2512
2670
|
}
|
|
@@ -2655,6 +2813,9 @@ class HarmonyApiClient {
|
|
|
2655
2813
|
title
|
|
2656
2814
|
});
|
|
2657
2815
|
}
|
|
2816
|
+
async removeExternalLink(cardId, linkId) {
|
|
2817
|
+
return this.request("DELETE", `/cards/${cardId}/external-links/${linkId}`);
|
|
2818
|
+
}
|
|
2658
2819
|
async uploadArtifact(data) {
|
|
2659
2820
|
return this.request("POST", "/artifacts", data);
|
|
2660
2821
|
}
|
|
@@ -2747,7 +2908,10 @@ class HarmonyApiClient {
|
|
|
2747
2908
|
return this.request("DELETE", `/cards/${cardId}/agent-context`, data);
|
|
2748
2909
|
}
|
|
2749
2910
|
async appendAgentRunEvents(cardId, data) {
|
|
2750
|
-
return this.request("POST", `/cards/${cardId}/agent-run-events`,
|
|
2911
|
+
return this.request("POST", `/cards/${cardId}/agent-run-events`, {
|
|
2912
|
+
...data,
|
|
2913
|
+
events: data.events.map((event) => sanitizeRunEventDraft(event))
|
|
2914
|
+
});
|
|
2751
2915
|
}
|
|
2752
2916
|
async getPendingUserMessages(cardId, sessionId, sinceSeq) {
|
|
2753
2917
|
return this.request("GET", `/cards/${cardId}/agent-messages?sessionId=${sessionId}&sinceSeq=${sinceSeq}`);
|
|
@@ -4400,81 +4564,6 @@ function parseHmyConfig(text) {
|
|
|
4400
4564
|
}
|
|
4401
4565
|
|
|
4402
4566
|
// src/skills.ts
|
|
4403
|
-
var HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
|
|
4404
|
-
|
|
4405
|
-
Start work on a Harmony card. Card reference: $ARGUMENTS
|
|
4406
|
-
|
|
4407
|
-
## 1. Find & Fetch Card
|
|
4408
|
-
|
|
4409
|
-
Parse the reference and fetch the card:
|
|
4410
|
-
- \`#42\` or \`42\` → \`harmony_get_card\` with \`shortId: 42\`
|
|
4411
|
-
- UUID → \`harmony_get_card\` with \`cardId\`
|
|
4412
|
-
- Name/text → \`harmony_search_cards\` with \`query\`
|
|
4413
|
-
|
|
4414
|
-
## 2. Get Board State
|
|
4415
|
-
|
|
4416
|
-
Call \`harmony_get_board\` to get columns and labels. From the response:
|
|
4417
|
-
- Find the "In Progress" (or "Progress") column ID
|
|
4418
|
-
- Find the "agent" label ID
|
|
4419
|
-
|
|
4420
|
-
## 3. Setup Card for Work
|
|
4421
|
-
|
|
4422
|
-
Execute these in sequence:
|
|
4423
|
-
1. \`harmony_move_card\` → Move to "In Progress" column
|
|
4424
|
-
2. \`harmony_add_label_to_card\` → Add "agent" label
|
|
4425
|
-
3. \`harmony_start_agent_session\`:
|
|
4426
|
-
- \`cardId\`: Card UUID
|
|
4427
|
-
- \`agentIdentifier\`: Your agent identifier
|
|
4428
|
-
- \`agentName\`: Your agent name
|
|
4429
|
-
- \`currentTask\`: "Analyzing card requirements"
|
|
4430
|
-
|
|
4431
|
-
## 4. Generate Work Prompt
|
|
4432
|
-
|
|
4433
|
-
Call \`harmony_generate_prompt\` with:
|
|
4434
|
-
- \`cardId\` or \`shortId\` (+ \`projectId\` if using shortId)
|
|
4435
|
-
- \`variant\`: Select based on task:
|
|
4436
|
-
- \`"execute"\` (default) → Clear tasks, bug fixes, well-defined work
|
|
4437
|
-
- \`"analysis"\` → Complex features, unclear requirements
|
|
4438
|
-
- \`"draft"\` → Medium complexity, want feedback first
|
|
4439
|
-
|
|
4440
|
-
The generated prompt provides role framing, focus areas, subtasks, linked cards, and suggested outputs.
|
|
4441
|
-
|
|
4442
|
-
## 5. Display Card Summary
|
|
4443
|
-
|
|
4444
|
-
Show the user: Card title, short ID, role, priority, labels, due date, description, and subtasks.
|
|
4445
|
-
|
|
4446
|
-
## 6. Implement Solution
|
|
4447
|
-
|
|
4448
|
-
Work on the card following the generated prompt's guidance. Update progress at milestones:
|
|
4449
|
-
- \`harmony_update_agent_progress\` with \`progressPercent\` (0-100), \`currentTask\`, \`status\`, \`blockers\`
|
|
4450
|
-
|
|
4451
|
-
**Progress checkpoints:** 20% (exploration), 50% (implementation), 80% (testing), 100% (done)
|
|
4452
|
-
|
|
4453
|
-
## 7. Complete Work
|
|
4454
|
-
|
|
4455
|
-
When finished:
|
|
4456
|
-
1. \`harmony_end_agent_session\` with \`status: "completed"\`, \`progressPercent: 100\`
|
|
4457
|
-
2. \`harmony_move_card\` to "Review" column
|
|
4458
|
-
3. Summarize accomplishments
|
|
4459
|
-
|
|
4460
|
-
If pausing: \`harmony_end_agent_session\` with \`status: "paused"\`
|
|
4461
|
-
|
|
4462
|
-
## Key Tools Reference
|
|
4463
|
-
|
|
4464
|
-
**Cards:** \`harmony_get_card\` (by \`cardId\`, \`shortId\`, or \`shortIds\`), \`harmony_search_cards\`, \`harmony_create_card\`, \`harmony_update_card\`, \`harmony_move_card\`, \`harmony_delete_card\`, \`harmony_assign_card\`
|
|
4465
|
-
|
|
4466
|
-
**Subtasks:** \`harmony_create_subtask\`, \`harmony_toggle_subtask\`, \`harmony_delete_subtask\`
|
|
4467
|
-
|
|
4468
|
-
**Labels:** \`harmony_add_label_to_card\`, \`harmony_remove_label_from_card\`, \`harmony_create_label\`
|
|
4469
|
-
|
|
4470
|
-
**Links:** \`harmony_add_link_to_card\`, \`harmony_remove_link_from_card\`, \`harmony_get_card_links\`
|
|
4471
|
-
|
|
4472
|
-
**Board:** \`harmony_get_board\`, \`harmony_list_projects\`, \`harmony_get_context\`, \`harmony_set_project_context\`
|
|
4473
|
-
|
|
4474
|
-
**Sessions:** \`harmony_start_agent_session\`, \`harmony_update_agent_progress\`, \`harmony_end_agent_session\`, \`harmony_get_agent_session\`
|
|
4475
|
-
|
|
4476
|
-
**AI:** \`harmony_generate_prompt\`, \`harmony_process_command\`
|
|
4477
|
-
`;
|
|
4478
4567
|
function buildSkillFile(skill) {
|
|
4479
4568
|
const content = stripSkillPreamble(skill.content);
|
|
4480
4569
|
if (skill.skillVersion !== undefined && !hasMetadataVersion(content)) {
|
|
@@ -5437,6 +5526,23 @@ var TOOLS = {
|
|
|
5437
5526
|
required: ["cardId", "url"]
|
|
5438
5527
|
}
|
|
5439
5528
|
},
|
|
5529
|
+
harmony_remove_external_link: {
|
|
5530
|
+
description: "Remove an external reference URL from a card — the counterpart to harmony_add_external_link. Takes the link id from harmony_get_card_external_links, not the URL.",
|
|
5531
|
+
inputSchema: {
|
|
5532
|
+
type: "object",
|
|
5533
|
+
properties: {
|
|
5534
|
+
cardId: {
|
|
5535
|
+
type: "string",
|
|
5536
|
+
description: "Card UUID"
|
|
5537
|
+
},
|
|
5538
|
+
linkId: {
|
|
5539
|
+
type: "string",
|
|
5540
|
+
description: "External link UUID, as returned by harmony_get_card_external_links"
|
|
5541
|
+
}
|
|
5542
|
+
},
|
|
5543
|
+
required: ["cardId", "linkId"]
|
|
5544
|
+
}
|
|
5545
|
+
},
|
|
5440
5546
|
harmony_create_subtask: {
|
|
5441
5547
|
description: "Create a subtask on a card",
|
|
5442
5548
|
inputSchema: {
|
|
@@ -6312,7 +6418,7 @@ var TOOLS = {
|
|
|
6312
6418
|
}
|
|
6313
6419
|
},
|
|
6314
6420
|
harmony_create_plan: {
|
|
6315
|
-
description: "Create a new project plan. Use this to upload
|
|
6421
|
+
description: "Create a new project plan. Use this to upload a plan written during planning. Returns a URL where the plan can be viewed and edited in Harmony.",
|
|
6316
6422
|
inputSchema: {
|
|
6317
6423
|
type: "object",
|
|
6318
6424
|
properties: {
|
|
@@ -6335,7 +6441,10 @@ var TOOLS = {
|
|
|
6335
6441
|
items: {
|
|
6336
6442
|
type: "object",
|
|
6337
6443
|
properties: {
|
|
6338
|
-
content: {
|
|
6444
|
+
content: {
|
|
6445
|
+
type: "string",
|
|
6446
|
+
description: 'One success criterion, as a statement about the finished product that a test can check ("the mirror matches the migration chain"), never a work package ("write the mirror script"). One criterion may take several cards.'
|
|
6447
|
+
},
|
|
6339
6448
|
priority: {
|
|
6340
6449
|
type: "string",
|
|
6341
6450
|
enum: ["high", "medium", "low"],
|
|
@@ -6349,7 +6458,7 @@ var TOOLS = {
|
|
|
6349
6458
|
},
|
|
6350
6459
|
required: ["content"]
|
|
6351
6460
|
},
|
|
6352
|
-
description: "
|
|
6461
|
+
description: "The plan's success criteria, one entry each - what must be true when the plan is done, not a breakdown of the work to do it."
|
|
6353
6462
|
}
|
|
6354
6463
|
},
|
|
6355
6464
|
required: ["title"]
|
|
@@ -6370,7 +6479,7 @@ var TOOLS = {
|
|
|
6370
6479
|
}
|
|
6371
6480
|
},
|
|
6372
6481
|
harmony_update_plan: {
|
|
6373
|
-
description: "Update an existing plan
|
|
6482
|
+
description: "Update an existing plan: its title, content, status, or the timeline dates its bar spans. " + "`startDate`/`endDate` are the plan's OWN schedule, the same pair a person sets by dragging the bar in the timeline view. " + "A plan is pinned on both or on neither: send both to schedule it, or both as null to return it to the span derived from its linked cards. " + "Sending one alone is refused unless the plan is already pinned. " + "They are never adjusted automatically — a card running past `endDate` is drawn as an overrun, and only a person extends the plan.",
|
|
6374
6483
|
inputSchema: {
|
|
6375
6484
|
type: "object",
|
|
6376
6485
|
properties: {
|
|
@@ -6384,6 +6493,16 @@ var TOOLS = {
|
|
|
6384
6493
|
type: "string",
|
|
6385
6494
|
enum: ["draft", "active", "archived"],
|
|
6386
6495
|
description: "New status"
|
|
6496
|
+
},
|
|
6497
|
+
startDate: {
|
|
6498
|
+
type: "string",
|
|
6499
|
+
nullable: true,
|
|
6500
|
+
description: "Timeline start as YYYY-MM-DD, or null to unpin (send endDate null too)."
|
|
6501
|
+
},
|
|
6502
|
+
endDate: {
|
|
6503
|
+
type: "string",
|
|
6504
|
+
nullable: true,
|
|
6505
|
+
description: "Timeline end as YYYY-MM-DD, or null to unpin (send startDate null too). Must not precede startDate."
|
|
6387
6506
|
}
|
|
6388
6507
|
},
|
|
6389
6508
|
required: ["planId"]
|
|
@@ -7331,6 +7450,11 @@ ${list}
|
|
|
7331
7450
|
const result = await client3.addExternalLink(cardId, url, title);
|
|
7332
7451
|
return { success: true, ...result };
|
|
7333
7452
|
}
|
|
7453
|
+
case "harmony_remove_external_link": {
|
|
7454
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
7455
|
+
const linkId = z.string().uuid().parse(args.linkId);
|
|
7456
|
+
return await client3.removeExternalLink(cardId, linkId);
|
|
7457
|
+
}
|
|
7334
7458
|
case "harmony_classify_card":
|
|
7335
7459
|
return deprecatedRemovedToolResult("harmony_classify_card");
|
|
7336
7460
|
case "harmony_create_subtask": {
|
|
@@ -8363,6 +8487,13 @@ ${options}
|
|
|
8363
8487
|
if (args.status !== undefined) {
|
|
8364
8488
|
updates.status = z.enum(["draft", "active", "archived"]).parse(args.status);
|
|
8365
8489
|
}
|
|
8490
|
+
const planDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, {
|
|
8491
|
+
message: "expected a date as YYYY-MM-DD"
|
|
8492
|
+
});
|
|
8493
|
+
if (args.startDate !== undefined)
|
|
8494
|
+
updates.startDate = planDate.nullable().parse(args.startDate);
|
|
8495
|
+
if (args.endDate !== undefined)
|
|
8496
|
+
updates.endDate = planDate.nullable().parse(args.endDate);
|
|
8366
8497
|
const result = await client3.updatePlan(planId, updates);
|
|
8367
8498
|
return { success: true, plan: result.plan };
|
|
8368
8499
|
}
|