@gethmy/mcp 2.8.0 → 2.8.2
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 +617 -276
- package/dist/index.js +525 -5
- package/dist/lib/api-client.js +121 -0
- package/package.json +2 -2
- package/src/api-client.ts +66 -1
- package/src/cli.ts +2 -2
- package/src/hmy-config.ts +70 -0
- package/src/server.ts +158 -3
- package/src/skills.ts +115 -19
- package/src/tui/setup.ts +8 -8
package/dist/cli.js
CHANGED
|
@@ -1121,6 +1121,87 @@ function getDisplayLinkType(linkType, direction) {
|
|
|
1121
1121
|
return linkType;
|
|
1122
1122
|
return LINK_TYPE_INVERSES[linkType];
|
|
1123
1123
|
}
|
|
1124
|
+
// ../harmony-shared/dist/commentSerializer.js
|
|
1125
|
+
var CONFLICT_INSTRUCTION = "When two comments conflict, prefer the latest created_at, UNLESS a later " + "comment explicitly confirms or restates the earlier finding. Evaluate " + "substance, not just recency. Cite the comment id(s) you relied on.";
|
|
1126
|
+
function authorLabel(c) {
|
|
1127
|
+
if (c.author_type === "agent")
|
|
1128
|
+
return "AI agent";
|
|
1129
|
+
return c.author?.full_name || c.author?.email || "teammate";
|
|
1130
|
+
}
|
|
1131
|
+
function criticalIds(comments) {
|
|
1132
|
+
const keep = new Set;
|
|
1133
|
+
for (const c of comments) {
|
|
1134
|
+
if (c.comment_type === "decision")
|
|
1135
|
+
keep.add(c.id);
|
|
1136
|
+
if (c.supersedes_id) {
|
|
1137
|
+
keep.add(c.id);
|
|
1138
|
+
keep.add(c.supersedes_id);
|
|
1139
|
+
}
|
|
1140
|
+
if (c.confirms_id) {
|
|
1141
|
+
keep.add(c.id);
|
|
1142
|
+
keep.add(c.confirms_id);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return keep;
|
|
1146
|
+
}
|
|
1147
|
+
function serializeCommentThread(comments, options = {}) {
|
|
1148
|
+
const { heading = "Conversation", includeInstructions = true, activity = [], maxComments } = options;
|
|
1149
|
+
const visible = comments.filter((c) => !c.deleted_at).slice().sort((a, b) => a.created_at.localeCompare(b.created_at));
|
|
1150
|
+
if (visible.length === 0)
|
|
1151
|
+
return "";
|
|
1152
|
+
const indexById = new Map;
|
|
1153
|
+
visible.forEach((c, i) => {
|
|
1154
|
+
indexById.set(c.id, i + 1);
|
|
1155
|
+
});
|
|
1156
|
+
let rendered = visible;
|
|
1157
|
+
let elidedCount = 0;
|
|
1158
|
+
if (maxComments && visible.length > maxComments) {
|
|
1159
|
+
const keep = criticalIds(visible);
|
|
1160
|
+
const recentThreshold = visible.length - maxComments;
|
|
1161
|
+
rendered = visible.filter((c, i) => i >= recentThreshold || keep.has(c.id));
|
|
1162
|
+
elidedCount = visible.length - rendered.length;
|
|
1163
|
+
}
|
|
1164
|
+
const ref = (id) => {
|
|
1165
|
+
const n = indexById.get(id);
|
|
1166
|
+
return n ? `#${n}` : `#${id.slice(0, 8)}`;
|
|
1167
|
+
};
|
|
1168
|
+
const lines = [];
|
|
1169
|
+
if (elidedCount > 0) {
|
|
1170
|
+
lines.push({
|
|
1171
|
+
at: visible[0]?.created_at ?? "",
|
|
1172
|
+
text: `(${elidedCount} earlier comment(s) omitted for brevity)`
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
for (const c of rendered) {
|
|
1176
|
+
const tags = [];
|
|
1177
|
+
if (c.edited_at)
|
|
1178
|
+
tags.push("edited");
|
|
1179
|
+
if (c.supersedes_id)
|
|
1180
|
+
tags.push(`supersedes ${ref(c.supersedes_id)}`);
|
|
1181
|
+
if (c.confirms_id)
|
|
1182
|
+
tags.push(`confirms ${ref(c.confirms_id)}`);
|
|
1183
|
+
if (c.resolved_at)
|
|
1184
|
+
tags.push("resolved");
|
|
1185
|
+
const tagStr = tags.length ? ` | ${tags.join(" | ")}` : "";
|
|
1186
|
+
const header = `[${ref(c.id)} | ${c.author_type} | ${authorLabel(c)} | ${c.comment_type} | ${c.created_at}${tagStr}]`;
|
|
1187
|
+
lines.push({ at: c.created_at, text: `${header}
|
|
1188
|
+
${c.body.trim()}` });
|
|
1189
|
+
}
|
|
1190
|
+
for (const a of activity) {
|
|
1191
|
+
const actor = a.actor ? `${a.actor} ` : "";
|
|
1192
|
+
lines.push({ at: a.at, text: `· (system) ${a.at} — ${actor}${a.text}` });
|
|
1193
|
+
}
|
|
1194
|
+
lines.sort((a, b) => a.at.localeCompare(b.at));
|
|
1195
|
+
const body = lines.map((l) => l.text).join(`
|
|
1196
|
+
|
|
1197
|
+
`);
|
|
1198
|
+
const instruction = includeInstructions ? `
|
|
1199
|
+
|
|
1200
|
+
${CONFLICT_INSTRUCTION}` : "";
|
|
1201
|
+
return `## ${heading} (oldest → newest)
|
|
1202
|
+
|
|
1203
|
+
${body}${instruction}`;
|
|
1204
|
+
}
|
|
1124
1205
|
// ../harmony-shared/dist/constants.js
|
|
1125
1206
|
var TIMINGS = {
|
|
1126
1207
|
SEARCH_DEBOUNCE: 300,
|
|
@@ -1473,6 +1554,28 @@ class HarmonyApiClient {
|
|
|
1473
1554
|
async deleteSubtask(subtaskId) {
|
|
1474
1555
|
return this.request("DELETE", `/subtasks/${subtaskId}`);
|
|
1475
1556
|
}
|
|
1557
|
+
async addComment(cardId, body, opts) {
|
|
1558
|
+
return this.request("POST", `/cards/${cardId}/comments`, {
|
|
1559
|
+
body,
|
|
1560
|
+
authorType: "agent",
|
|
1561
|
+
commentType: opts?.commentType,
|
|
1562
|
+
supersedesId: opts?.supersedesId,
|
|
1563
|
+
confirmsId: opts?.confirmsId,
|
|
1564
|
+
agentSessionId: opts?.agentSessionId
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
async getComments(cardId, opts) {
|
|
1568
|
+
const qs = new URLSearchParams;
|
|
1569
|
+
if (opts?.limit != null)
|
|
1570
|
+
qs.set("limit", String(opts.limit));
|
|
1571
|
+
if (opts?.offset != null)
|
|
1572
|
+
qs.set("offset", String(opts.offset));
|
|
1573
|
+
const suffix = qs.toString() ? `?${qs.toString()}` : "";
|
|
1574
|
+
return this.request("GET", `/cards/${cardId}/comments${suffix}`);
|
|
1575
|
+
}
|
|
1576
|
+
async updateComment(commentId, updates) {
|
|
1577
|
+
return this.request("PATCH", `/comments/${commentId}`, updates);
|
|
1578
|
+
}
|
|
1476
1579
|
async startAgentSession(cardId, data) {
|
|
1477
1580
|
return this.request("POST", `/cards/${cardId}/agent-context`, data);
|
|
1478
1581
|
}
|
|
@@ -1830,6 +1933,24 @@ class HarmonyApiClient {
|
|
|
1830
1933
|
assembledContext: assembledContextStr,
|
|
1831
1934
|
assemblyId
|
|
1832
1935
|
});
|
|
1936
|
+
try {
|
|
1937
|
+
const { comments } = await this.getComments(options.cardId, {
|
|
1938
|
+
limit: 200
|
|
1939
|
+
});
|
|
1940
|
+
if (Array.isArray(comments) && comments.length > 0) {
|
|
1941
|
+
const section = serializeCommentThread(comments, {
|
|
1942
|
+
heading: "Comments",
|
|
1943
|
+
maxComments: 40
|
|
1944
|
+
});
|
|
1945
|
+
if (section)
|
|
1946
|
+
result.prompt = `${result.prompt}
|
|
1947
|
+
|
|
1948
|
+
${section}`;
|
|
1949
|
+
}
|
|
1950
|
+
} catch (err) {
|
|
1951
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1952
|
+
console.debug(`[generateCardPrompt] comments fetch failed: ${msg}`);
|
|
1953
|
+
}
|
|
1833
1954
|
try {
|
|
1834
1955
|
await this.recordPromptHistory({
|
|
1835
1956
|
cardId: cardData.id,
|
|
@@ -2378,6 +2499,294 @@ async function onboardNewUser(params) {
|
|
|
2378
2499
|
};
|
|
2379
2500
|
}
|
|
2380
2501
|
|
|
2502
|
+
// src/skills.ts
|
|
2503
|
+
import {
|
|
2504
|
+
existsSync as existsSync4,
|
|
2505
|
+
mkdirSync as mkdirSync3,
|
|
2506
|
+
readFileSync as readFileSync4,
|
|
2507
|
+
renameSync,
|
|
2508
|
+
writeFileSync as writeFileSync3
|
|
2509
|
+
} from "node:fs";
|
|
2510
|
+
import { homedir as homedir3 } from "node:os";
|
|
2511
|
+
import { dirname, join as join4 } from "node:path";
|
|
2512
|
+
|
|
2513
|
+
// src/hmy-config.ts
|
|
2514
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
2515
|
+
import { homedir as homedir2 } from "node:os";
|
|
2516
|
+
import { join as join3 } from "node:path";
|
|
2517
|
+
var DEFAULTS = { updateCheck: true, pin: null };
|
|
2518
|
+
function getHmyConfigPath() {
|
|
2519
|
+
return join3(homedir2(), ".hmy", "config.yaml");
|
|
2520
|
+
}
|
|
2521
|
+
function loadHmyConfig() {
|
|
2522
|
+
const path = getHmyConfigPath();
|
|
2523
|
+
if (!existsSync3(path))
|
|
2524
|
+
return { ...DEFAULTS };
|
|
2525
|
+
try {
|
|
2526
|
+
return parseHmyConfig(readFileSync3(path, "utf-8"));
|
|
2527
|
+
} catch {
|
|
2528
|
+
return { ...DEFAULTS };
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
function parseHmyConfig(text) {
|
|
2532
|
+
const cfg = { ...DEFAULTS };
|
|
2533
|
+
for (const rawLine of text.split(`
|
|
2534
|
+
`)) {
|
|
2535
|
+
const line = rawLine.trim();
|
|
2536
|
+
if (!line || line.startsWith("#"))
|
|
2537
|
+
continue;
|
|
2538
|
+
const sep2 = line.indexOf(":");
|
|
2539
|
+
if (sep2 === -1)
|
|
2540
|
+
continue;
|
|
2541
|
+
const key = line.slice(0, sep2).trim();
|
|
2542
|
+
let value = line.slice(sep2 + 1).trim();
|
|
2543
|
+
const hash = value.indexOf(" #");
|
|
2544
|
+
if (hash !== -1)
|
|
2545
|
+
value = value.slice(0, hash).trim();
|
|
2546
|
+
value = value.replace(/^["']|["']$/g, "");
|
|
2547
|
+
switch (key) {
|
|
2548
|
+
case "update_check":
|
|
2549
|
+
cfg.updateCheck = value !== "false";
|
|
2550
|
+
break;
|
|
2551
|
+
case "pin":
|
|
2552
|
+
case "pin_version":
|
|
2553
|
+
cfg.pin = value || null;
|
|
2554
|
+
break;
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
return cfg;
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
// src/skills.ts
|
|
2561
|
+
var HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
|
|
2562
|
+
|
|
2563
|
+
Start work on a Harmony card. Card reference: $ARGUMENTS
|
|
2564
|
+
|
|
2565
|
+
## 1. Find & Fetch Card
|
|
2566
|
+
|
|
2567
|
+
Parse the reference and fetch the card:
|
|
2568
|
+
- \`#42\` or \`42\` → \`harmony_get_card_by_short_id\` with \`shortId: 42\`
|
|
2569
|
+
- UUID → \`harmony_get_card\` with \`cardId\`
|
|
2570
|
+
- Name/text → \`harmony_search_cards\` with \`query\`
|
|
2571
|
+
|
|
2572
|
+
## 2. Get Board State
|
|
2573
|
+
|
|
2574
|
+
Call \`harmony_get_board\` to get columns and labels. From the response:
|
|
2575
|
+
- Find the "In Progress" (or "Progress") column ID
|
|
2576
|
+
- Find the "agent" label ID
|
|
2577
|
+
|
|
2578
|
+
## 3. Setup Card for Work
|
|
2579
|
+
|
|
2580
|
+
Execute these in sequence:
|
|
2581
|
+
1. \`harmony_move_card\` → Move to "In Progress" column
|
|
2582
|
+
2. \`harmony_add_label_to_card\` → Add "agent" label
|
|
2583
|
+
3. \`harmony_start_agent_session\`:
|
|
2584
|
+
- \`cardId\`: Card UUID
|
|
2585
|
+
- \`agentIdentifier\`: Your agent identifier
|
|
2586
|
+
- \`agentName\`: Your agent name
|
|
2587
|
+
- \`currentTask\`: "Analyzing card requirements"
|
|
2588
|
+
|
|
2589
|
+
## 4. Generate Work Prompt
|
|
2590
|
+
|
|
2591
|
+
Call \`harmony_generate_prompt\` with:
|
|
2592
|
+
- \`cardId\` or \`shortId\` (+ \`projectId\` if using shortId)
|
|
2593
|
+
- \`variant\`: Select based on task:
|
|
2594
|
+
- \`"execute"\` (default) → Clear tasks, bug fixes, well-defined work
|
|
2595
|
+
- \`"analysis"\` → Complex features, unclear requirements
|
|
2596
|
+
- \`"draft"\` → Medium complexity, want feedback first
|
|
2597
|
+
|
|
2598
|
+
The generated prompt provides role framing, focus areas, subtasks, linked cards, and suggested outputs.
|
|
2599
|
+
|
|
2600
|
+
## 5. Display Card Summary
|
|
2601
|
+
|
|
2602
|
+
Show the user: Card title, short ID, role, priority, labels, due date, description, and subtasks.
|
|
2603
|
+
|
|
2604
|
+
## 6. Implement Solution
|
|
2605
|
+
|
|
2606
|
+
Work on the card following the generated prompt's guidance. Update progress at milestones:
|
|
2607
|
+
- \`harmony_update_agent_progress\` with \`progressPercent\` (0-100), \`currentTask\`, \`status\`, \`blockers\`
|
|
2608
|
+
|
|
2609
|
+
**Progress checkpoints:** 20% (exploration), 50% (implementation), 80% (testing), 100% (done)
|
|
2610
|
+
|
|
2611
|
+
## 7. Complete Work
|
|
2612
|
+
|
|
2613
|
+
When finished:
|
|
2614
|
+
1. \`harmony_end_agent_session\` with \`status: "completed"\`, \`progressPercent: 100\`
|
|
2615
|
+
2. \`harmony_move_card\` to "Review" column
|
|
2616
|
+
3. Summarize accomplishments
|
|
2617
|
+
|
|
2618
|
+
If pausing: \`harmony_end_agent_session\` with \`status: "paused"\`
|
|
2619
|
+
|
|
2620
|
+
## Key Tools Reference
|
|
2621
|
+
|
|
2622
|
+
**Cards:** \`harmony_get_card\`, \`harmony_get_card_by_short_id\`, \`harmony_search_cards\`, \`harmony_create_card\`, \`harmony_update_card\`, \`harmony_move_card\`, \`harmony_delete_card\`, \`harmony_assign_card\`
|
|
2623
|
+
|
|
2624
|
+
**Subtasks:** \`harmony_create_subtask\`, \`harmony_toggle_subtask\`, \`harmony_delete_subtask\`
|
|
2625
|
+
|
|
2626
|
+
**Labels:** \`harmony_add_label_to_card\`, \`harmony_remove_label_from_card\`, \`harmony_create_label\`
|
|
2627
|
+
|
|
2628
|
+
**Links:** \`harmony_add_link_to_card\`, \`harmony_remove_link_from_card\`, \`harmony_get_card_links\`
|
|
2629
|
+
|
|
2630
|
+
**Board:** \`harmony_get_board\`, \`harmony_list_projects\`, \`harmony_get_context\`, \`harmony_set_project_context\`
|
|
2631
|
+
|
|
2632
|
+
**Sessions:** \`harmony_start_agent_session\`, \`harmony_update_agent_progress\`, \`harmony_end_agent_session\`, \`harmony_get_agent_session\`
|
|
2633
|
+
|
|
2634
|
+
**AI:** \`harmony_generate_prompt\`, \`harmony_process_command\`
|
|
2635
|
+
`;
|
|
2636
|
+
function buildSkillFile(skill) {
|
|
2637
|
+
const content = stripSkillPreamble(skill.content);
|
|
2638
|
+
if (skill.skillVersion !== undefined && !hasMetadataVersion(content)) {
|
|
2639
|
+
return injectMetadataVersion(content, skill.skillVersion);
|
|
2640
|
+
}
|
|
2641
|
+
return content;
|
|
2642
|
+
}
|
|
2643
|
+
var PREAMBLE_START = "<!-- hmy-skills-preamble:start -->";
|
|
2644
|
+
var PREAMBLE_END = "<!-- hmy-skills-preamble:end -->";
|
|
2645
|
+
function stripSkillPreamble(content) {
|
|
2646
|
+
const start = content.indexOf(PREAMBLE_START);
|
|
2647
|
+
const end = content.indexOf(PREAMBLE_END);
|
|
2648
|
+
if (start === -1 || end === -1 || end < start)
|
|
2649
|
+
return content;
|
|
2650
|
+
const stripped = content.slice(0, start) + content.slice(end + PREAMBLE_END.length);
|
|
2651
|
+
return `${stripped.replace(/\n{3,}/g, `
|
|
2652
|
+
|
|
2653
|
+
`).replace(/\s+$/, "")}
|
|
2654
|
+
`;
|
|
2655
|
+
}
|
|
2656
|
+
function atomicWrite(filePath, content) {
|
|
2657
|
+
const dir = dirname(filePath);
|
|
2658
|
+
if (!existsSync4(dir))
|
|
2659
|
+
mkdirSync3(dir, { recursive: true });
|
|
2660
|
+
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
2661
|
+
writeFileSync3(tmp, content);
|
|
2662
|
+
renameSync(tmp, filePath);
|
|
2663
|
+
}
|
|
2664
|
+
function hasMetadataVersion(content) {
|
|
2665
|
+
return parseSkillVersion(content) !== null;
|
|
2666
|
+
}
|
|
2667
|
+
function injectMetadataVersion(content, version) {
|
|
2668
|
+
const fmMatch = content.match(/^(---\n[\s\S]*?\n)(---\n)([\s\S]*)$/);
|
|
2669
|
+
if (!fmMatch)
|
|
2670
|
+
return content;
|
|
2671
|
+
const [, head, close, body] = fmMatch;
|
|
2672
|
+
const block = `metadata:
|
|
2673
|
+
version: "${version}"
|
|
2674
|
+
`;
|
|
2675
|
+
return `${head}${block}${close}${body}`;
|
|
2676
|
+
}
|
|
2677
|
+
function parseSkillVersion(content) {
|
|
2678
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
2679
|
+
if (fmMatch) {
|
|
2680
|
+
const fm = fmMatch[1];
|
|
2681
|
+
const verMatch = fm.match(/^metadata:[\s\S]*?\n[ \t]+version:[ \t]*["']?(\d+)["']?\s*$/m);
|
|
2682
|
+
if (verMatch)
|
|
2683
|
+
return verMatch[1];
|
|
2684
|
+
}
|
|
2685
|
+
const legacy = content.match(/<!-- skills-version:(\d+) -->/);
|
|
2686
|
+
return legacy ? legacy[1] : null;
|
|
2687
|
+
}
|
|
2688
|
+
function findSkillFiles(paths, knownNames) {
|
|
2689
|
+
const results = [];
|
|
2690
|
+
for (const filePath of paths) {
|
|
2691
|
+
if (!existsSync4(filePath))
|
|
2692
|
+
continue;
|
|
2693
|
+
for (const name of knownNames) {
|
|
2694
|
+
if (filePath.includes(`/${name}/`) || filePath.includes(`/${name}.md`)) {
|
|
2695
|
+
results.push({ name, filePath });
|
|
2696
|
+
break;
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
return results;
|
|
2701
|
+
}
|
|
2702
|
+
var HMY_DIR = join4(homedir3(), ".hmy");
|
|
2703
|
+
var HMY_VERSION_FILE = join4(HMY_DIR, "VERSION");
|
|
2704
|
+
var LAST_CHECK_FILE = join4(HMY_DIR, "last-update-check");
|
|
2705
|
+
var CHECK_TTL_MS = 24 * 60 * 60 * 1000;
|
|
2706
|
+
function checkedRecently(now = Date.now()) {
|
|
2707
|
+
try {
|
|
2708
|
+
if (!existsSync4(LAST_CHECK_FILE))
|
|
2709
|
+
return false;
|
|
2710
|
+
const ts = Number.parseInt(readFileSync4(LAST_CHECK_FILE, "utf-8").trim(), 10);
|
|
2711
|
+
if (!Number.isFinite(ts))
|
|
2712
|
+
return false;
|
|
2713
|
+
return now - ts < CHECK_TTL_MS;
|
|
2714
|
+
} catch {
|
|
2715
|
+
return false;
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
function recordCheck(now = Date.now()) {
|
|
2719
|
+
try {
|
|
2720
|
+
if (!existsSync4(HMY_DIR))
|
|
2721
|
+
mkdirSync3(HMY_DIR, { recursive: true });
|
|
2722
|
+
writeFileSync3(LAST_CHECK_FILE, String(now));
|
|
2723
|
+
} catch {}
|
|
2724
|
+
}
|
|
2725
|
+
async function refreshSkills(opts = {}) {
|
|
2726
|
+
try {
|
|
2727
|
+
if (!isConfigured())
|
|
2728
|
+
return { updated: false };
|
|
2729
|
+
const cfg = loadHmyConfig();
|
|
2730
|
+
if (!cfg.updateCheck || cfg.pin)
|
|
2731
|
+
return { updated: false };
|
|
2732
|
+
if (!opts.force && checkedRecently())
|
|
2733
|
+
return { updated: false };
|
|
2734
|
+
const status = areSkillsInstalled();
|
|
2735
|
+
if (!status.installed)
|
|
2736
|
+
return { updated: false };
|
|
2737
|
+
const client3 = new HarmonyApiClient;
|
|
2738
|
+
const versionInfo = await client3.fetchSkillsVersion();
|
|
2739
|
+
recordCheck();
|
|
2740
|
+
const skillFiles = findSkillFiles(status.paths, versionInfo.skills);
|
|
2741
|
+
if (skillFiles.length > 0) {
|
|
2742
|
+
const samplePath = skillFiles[0].filePath;
|
|
2743
|
+
for (const name of versionInfo.skills) {
|
|
2744
|
+
if (skillFiles.some((sf) => sf.name === name))
|
|
2745
|
+
continue;
|
|
2746
|
+
let siblingPath;
|
|
2747
|
+
if (samplePath.endsWith("SKILL.md")) {
|
|
2748
|
+
const parentDir = dirname(dirname(samplePath));
|
|
2749
|
+
siblingPath = `${parentDir}/${name}/SKILL.md`;
|
|
2750
|
+
} else {
|
|
2751
|
+
const parentDir = dirname(samplePath);
|
|
2752
|
+
siblingPath = `${parentDir}/${name}.md`;
|
|
2753
|
+
}
|
|
2754
|
+
if (existsSync4(siblingPath)) {
|
|
2755
|
+
skillFiles.push({ name, filePath: siblingPath });
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
if (skillFiles.length === 0)
|
|
2760
|
+
return { updated: false };
|
|
2761
|
+
let updated = false;
|
|
2762
|
+
for (const { name, filePath } of skillFiles) {
|
|
2763
|
+
try {
|
|
2764
|
+
const currentContent = readFileSync4(filePath, "utf-8");
|
|
2765
|
+
const localVersion = parseSkillVersion(currentContent);
|
|
2766
|
+
const fetched = await client3.fetchSkill(name);
|
|
2767
|
+
const remoteVersion = fetched.skillVersion;
|
|
2768
|
+
if (remoteVersion !== undefined && localVersion !== null && Number(localVersion) >= remoteVersion) {
|
|
2769
|
+
continue;
|
|
2770
|
+
}
|
|
2771
|
+
atomicWrite(filePath, buildSkillFile(fetched));
|
|
2772
|
+
updated = true;
|
|
2773
|
+
} catch (err) {
|
|
2774
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2775
|
+
console.error(`Harmony: skill "${name}" refresh failed: ${msg}`);
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
if (updated) {
|
|
2779
|
+
try {
|
|
2780
|
+
atomicWrite(HMY_VERSION_FILE, versionInfo.version);
|
|
2781
|
+
} catch {}
|
|
2782
|
+
console.error("Harmony: Refreshed skills from server");
|
|
2783
|
+
}
|
|
2784
|
+
return { updated };
|
|
2785
|
+
} catch {
|
|
2786
|
+
return { updated: false };
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2381
2790
|
// src/server.ts
|
|
2382
2791
|
var memorySessions = new Map;
|
|
2383
2792
|
function parseLabelList(raw) {
|
|
@@ -2515,7 +2924,11 @@ var TOOLS = {
|
|
|
2515
2924
|
enum: ["low", "medium", "high", "urgent"],
|
|
2516
2925
|
description: "Priority level"
|
|
2517
2926
|
},
|
|
2518
|
-
assigneeId: { type: "string", description: "Assignee user ID" }
|
|
2927
|
+
assigneeId: { type: "string", description: "Assignee user ID" },
|
|
2928
|
+
planId: {
|
|
2929
|
+
type: "string",
|
|
2930
|
+
description: "Plan ID to link this card to (optional). Links the card to that plan via its plan_id."
|
|
2931
|
+
}
|
|
2519
2932
|
},
|
|
2520
2933
|
required: ["title"]
|
|
2521
2934
|
}
|
|
@@ -2840,6 +3253,66 @@ var TOOLS = {
|
|
|
2840
3253
|
required: ["subtaskId"]
|
|
2841
3254
|
}
|
|
2842
3255
|
},
|
|
3256
|
+
harmony_add_comment: {
|
|
3257
|
+
description: "Post a comment on a card as the agent. Use this to converse with the human in the open: report progress, ask a question, record a decision, or note a finding — instead of editing the card description. Set supersedesId to correct an earlier comment, confirmsId to reaffirm one. When the thread conflicts, prefer the latest comment unless a later one confirms an earlier finding; cite the comment id(s) you relied on.",
|
|
3258
|
+
inputSchema: {
|
|
3259
|
+
type: "object",
|
|
3260
|
+
properties: {
|
|
3261
|
+
cardId: { type: "string", description: "Card UUID to comment on" },
|
|
3262
|
+
body: { type: "string", description: "Comment body (Markdown)" },
|
|
3263
|
+
commentType: {
|
|
3264
|
+
type: "string",
|
|
3265
|
+
enum: [
|
|
3266
|
+
"message",
|
|
3267
|
+
"progress",
|
|
3268
|
+
"question",
|
|
3269
|
+
"blocker",
|
|
3270
|
+
"decision",
|
|
3271
|
+
"summary",
|
|
3272
|
+
"finding"
|
|
3273
|
+
],
|
|
3274
|
+
description: "Type of comment. 'question'/'blocker' signal you need a human; default 'message'."
|
|
3275
|
+
},
|
|
3276
|
+
supersedesId: {
|
|
3277
|
+
type: "string",
|
|
3278
|
+
description: "Comment id this comment corrects/updates"
|
|
3279
|
+
},
|
|
3280
|
+
confirmsId: {
|
|
3281
|
+
type: "string",
|
|
3282
|
+
description: "Comment id this comment reaffirms"
|
|
3283
|
+
}
|
|
3284
|
+
},
|
|
3285
|
+
required: ["cardId", "body"]
|
|
3286
|
+
}
|
|
3287
|
+
},
|
|
3288
|
+
harmony_get_comments: {
|
|
3289
|
+
description: "Get the comment thread on a card (oldest → newest). Returns human + agent comments with author, type, edited/resolved state, and supersede/confirm links. Read this before acting so you weigh later comments over earlier ones they contradict.",
|
|
3290
|
+
inputSchema: {
|
|
3291
|
+
type: "object",
|
|
3292
|
+
properties: {
|
|
3293
|
+
cardId: { type: "string" },
|
|
3294
|
+
limit: { type: "number" },
|
|
3295
|
+
offset: { type: "number" }
|
|
3296
|
+
},
|
|
3297
|
+
required: ["cardId"]
|
|
3298
|
+
}
|
|
3299
|
+
},
|
|
3300
|
+
harmony_update_comment: {
|
|
3301
|
+
description: "Update one of your own comments: edit the body, pin it, or resolve a question/blocker once it has been answered.",
|
|
3302
|
+
inputSchema: {
|
|
3303
|
+
type: "object",
|
|
3304
|
+
properties: {
|
|
3305
|
+
commentId: { type: "string" },
|
|
3306
|
+
body: { type: "string" },
|
|
3307
|
+
pinned: { type: "boolean" },
|
|
3308
|
+
resolve: {
|
|
3309
|
+
type: "boolean",
|
|
3310
|
+
description: "Mark (true) or clear (false) the resolved state"
|
|
3311
|
+
}
|
|
3312
|
+
},
|
|
3313
|
+
required: ["commentId"]
|
|
3314
|
+
}
|
|
3315
|
+
},
|
|
2843
3316
|
harmony_list_workspaces: {
|
|
2844
3317
|
description: "List all workspaces the user has access to",
|
|
2845
3318
|
inputSchema: {
|
|
@@ -3830,7 +4303,7 @@ function registerHandlers(server, deps) {
|
|
|
3830
4303
|
{
|
|
3831
4304
|
uri,
|
|
3832
4305
|
mimeType: "text/markdown",
|
|
3833
|
-
text: fetched.content
|
|
4306
|
+
text: stripSkillPreamble(fetched.content)
|
|
3834
4307
|
}
|
|
3835
4308
|
]
|
|
3836
4309
|
};
|
|
@@ -3880,7 +4353,8 @@ async function handleToolCall(name, args, deps) {
|
|
|
3880
4353
|
columnId: args.columnId,
|
|
3881
4354
|
description: args.description,
|
|
3882
4355
|
priority: args.priority,
|
|
3883
|
-
assigneeId: args.assigneeId
|
|
4356
|
+
assigneeId: args.assigneeId,
|
|
4357
|
+
planId: args.planId
|
|
3884
4358
|
});
|
|
3885
4359
|
return { success: true, ...result };
|
|
3886
4360
|
}
|
|
@@ -4100,6 +4574,49 @@ async function handleToolCall(name, args, deps) {
|
|
|
4100
4574
|
await client3.deleteSubtask(subtaskId);
|
|
4101
4575
|
return { success: true };
|
|
4102
4576
|
}
|
|
4577
|
+
case "harmony_add_comment": {
|
|
4578
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
4579
|
+
const body = z.string().min(1).max(1e4).parse(args.body);
|
|
4580
|
+
const commentType = args.commentType !== undefined ? z.enum([
|
|
4581
|
+
"message",
|
|
4582
|
+
"progress",
|
|
4583
|
+
"question",
|
|
4584
|
+
"blocker",
|
|
4585
|
+
"decision",
|
|
4586
|
+
"summary",
|
|
4587
|
+
"finding"
|
|
4588
|
+
]).parse(args.commentType) : undefined;
|
|
4589
|
+
const supersedesId = args.supersedesId !== undefined ? z.string().uuid().parse(args.supersedesId) : undefined;
|
|
4590
|
+
const confirmsId = args.confirmsId !== undefined ? z.string().uuid().parse(args.confirmsId) : undefined;
|
|
4591
|
+
const result = await client3.addComment(cardId, body, {
|
|
4592
|
+
commentType,
|
|
4593
|
+
supersedesId,
|
|
4594
|
+
confirmsId
|
|
4595
|
+
});
|
|
4596
|
+
return { success: true, ...result };
|
|
4597
|
+
}
|
|
4598
|
+
case "harmony_get_comments": {
|
|
4599
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
4600
|
+
const limit = args.limit !== undefined ? z.number().int().min(1).max(500).parse(args.limit) : undefined;
|
|
4601
|
+
const offset = args.offset !== undefined ? z.number().int().min(0).parse(args.offset) : undefined;
|
|
4602
|
+
const result = await client3.getComments(cardId, { limit, offset });
|
|
4603
|
+
return { success: true, ...result };
|
|
4604
|
+
}
|
|
4605
|
+
case "harmony_update_comment": {
|
|
4606
|
+
const commentId = z.string().uuid().parse(args.commentId);
|
|
4607
|
+
const updates = {};
|
|
4608
|
+
if (args.body !== undefined) {
|
|
4609
|
+
updates.body = z.string().min(1).max(1e4).parse(args.body);
|
|
4610
|
+
}
|
|
4611
|
+
if (args.pinned !== undefined) {
|
|
4612
|
+
updates.pinned = z.boolean().parse(args.pinned);
|
|
4613
|
+
}
|
|
4614
|
+
if (args.resolve !== undefined) {
|
|
4615
|
+
updates.resolve = z.boolean().parse(args.resolve);
|
|
4616
|
+
}
|
|
4617
|
+
const result = await client3.updateComment(commentId, updates);
|
|
4618
|
+
return { success: true, ...result };
|
|
4619
|
+
}
|
|
4103
4620
|
case "harmony_list_workspaces": {
|
|
4104
4621
|
const result = await client3.listWorkspaces();
|
|
4105
4622
|
return { success: true, ...result };
|
|
@@ -4906,13 +5423,16 @@ function createConfigDeps() {
|
|
|
4906
5423
|
class HarmonyMCPServer {
|
|
4907
5424
|
server;
|
|
4908
5425
|
constructor() {
|
|
4909
|
-
this.server = new Server({ name: "@gethmy/mcp", version: "2.0.0" }, { capabilities: { tools: {}, resources: {} } });
|
|
5426
|
+
this.server = new Server({ name: "@gethmy/mcp", version: "2.0.0" }, { capabilities: { tools: {}, resources: { listChanged: true } } });
|
|
4910
5427
|
registerHandlers(this.server, createConfigDeps());
|
|
4911
5428
|
}
|
|
4912
|
-
async run() {
|
|
5429
|
+
async run(opts = {}) {
|
|
4913
5430
|
const transport = new StdioServerTransport;
|
|
4914
5431
|
await this.server.connect(transport);
|
|
4915
5432
|
console.error("Harmony MCP server running on stdio");
|
|
5433
|
+
if (opts.skillsUpdated) {
|
|
5434
|
+
this.server.sendResourceListChanged().catch(() => {});
|
|
5435
|
+
}
|
|
4916
5436
|
const configDeps = createConfigDeps();
|
|
4917
5437
|
initAutoSession(async (client3, cardId, status) => {
|
|
4918
5438
|
await runEndSessionPipeline(client3, configDeps, cardId, status);
|
|
@@ -4959,209 +5479,30 @@ class HarmonyMCPServer {
|
|
|
4959
5479
|
}
|
|
4960
5480
|
}
|
|
4961
5481
|
|
|
4962
|
-
// src/skills.ts
|
|
4963
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4964
|
-
import { dirname } from "node:path";
|
|
4965
|
-
var HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
|
|
4966
|
-
|
|
4967
|
-
Start work on a Harmony card. Card reference: $ARGUMENTS
|
|
4968
|
-
|
|
4969
|
-
## 1. Find & Fetch Card
|
|
4970
|
-
|
|
4971
|
-
Parse the reference and fetch the card:
|
|
4972
|
-
- \`#42\` or \`42\` → \`harmony_get_card_by_short_id\` with \`shortId: 42\`
|
|
4973
|
-
- UUID → \`harmony_get_card\` with \`cardId\`
|
|
4974
|
-
- Name/text → \`harmony_search_cards\` with \`query\`
|
|
4975
|
-
|
|
4976
|
-
## 2. Get Board State
|
|
4977
|
-
|
|
4978
|
-
Call \`harmony_get_board\` to get columns and labels. From the response:
|
|
4979
|
-
- Find the "In Progress" (or "Progress") column ID
|
|
4980
|
-
- Find the "agent" label ID
|
|
4981
|
-
|
|
4982
|
-
## 3. Setup Card for Work
|
|
4983
|
-
|
|
4984
|
-
Execute these in sequence:
|
|
4985
|
-
1. \`harmony_move_card\` → Move to "In Progress" column
|
|
4986
|
-
2. \`harmony_add_label_to_card\` → Add "agent" label
|
|
4987
|
-
3. \`harmony_start_agent_session\`:
|
|
4988
|
-
- \`cardId\`: Card UUID
|
|
4989
|
-
- \`agentIdentifier\`: Your agent identifier
|
|
4990
|
-
- \`agentName\`: Your agent name
|
|
4991
|
-
- \`currentTask\`: "Analyzing card requirements"
|
|
4992
|
-
|
|
4993
|
-
## 4. Generate Work Prompt
|
|
4994
|
-
|
|
4995
|
-
Call \`harmony_generate_prompt\` with:
|
|
4996
|
-
- \`cardId\` or \`shortId\` (+ \`projectId\` if using shortId)
|
|
4997
|
-
- \`variant\`: Select based on task:
|
|
4998
|
-
- \`"execute"\` (default) → Clear tasks, bug fixes, well-defined work
|
|
4999
|
-
- \`"analysis"\` → Complex features, unclear requirements
|
|
5000
|
-
- \`"draft"\` → Medium complexity, want feedback first
|
|
5001
|
-
|
|
5002
|
-
The generated prompt provides role framing, focus areas, subtasks, linked cards, and suggested outputs.
|
|
5003
|
-
|
|
5004
|
-
## 5. Display Card Summary
|
|
5005
|
-
|
|
5006
|
-
Show the user: Card title, short ID, role, priority, labels, due date, description, and subtasks.
|
|
5007
|
-
|
|
5008
|
-
## 6. Implement Solution
|
|
5009
|
-
|
|
5010
|
-
Work on the card following the generated prompt's guidance. Update progress at milestones:
|
|
5011
|
-
- \`harmony_update_agent_progress\` with \`progressPercent\` (0-100), \`currentTask\`, \`status\`, \`blockers\`
|
|
5012
|
-
|
|
5013
|
-
**Progress checkpoints:** 20% (exploration), 50% (implementation), 80% (testing), 100% (done)
|
|
5014
|
-
|
|
5015
|
-
## 7. Complete Work
|
|
5016
|
-
|
|
5017
|
-
When finished:
|
|
5018
|
-
1. \`harmony_end_agent_session\` with \`status: "completed"\`, \`progressPercent: 100\`
|
|
5019
|
-
2. \`harmony_move_card\` to "Review" column
|
|
5020
|
-
3. Summarize accomplishments
|
|
5021
|
-
|
|
5022
|
-
If pausing: \`harmony_end_agent_session\` with \`status: "paused"\`
|
|
5023
|
-
|
|
5024
|
-
## Key Tools Reference
|
|
5025
|
-
|
|
5026
|
-
**Cards:** \`harmony_get_card\`, \`harmony_get_card_by_short_id\`, \`harmony_search_cards\`, \`harmony_create_card\`, \`harmony_update_card\`, \`harmony_move_card\`, \`harmony_delete_card\`, \`harmony_assign_card\`
|
|
5027
|
-
|
|
5028
|
-
**Subtasks:** \`harmony_create_subtask\`, \`harmony_toggle_subtask\`, \`harmony_delete_subtask\`
|
|
5029
|
-
|
|
5030
|
-
**Labels:** \`harmony_add_label_to_card\`, \`harmony_remove_label_from_card\`, \`harmony_create_label\`
|
|
5031
|
-
|
|
5032
|
-
**Links:** \`harmony_add_link_to_card\`, \`harmony_remove_link_from_card\`, \`harmony_get_card_links\`
|
|
5033
|
-
|
|
5034
|
-
**Board:** \`harmony_get_board\`, \`harmony_list_projects\`, \`harmony_get_context\`, \`harmony_set_project_context\`
|
|
5035
|
-
|
|
5036
|
-
**Sessions:** \`harmony_start_agent_session\`, \`harmony_update_agent_progress\`, \`harmony_end_agent_session\`, \`harmony_get_agent_session\`
|
|
5037
|
-
|
|
5038
|
-
**AI:** \`harmony_generate_prompt\`, \`harmony_process_command\`
|
|
5039
|
-
`;
|
|
5040
|
-
function buildSkillFile(skill) {
|
|
5041
|
-
if (skill.skillVersion !== undefined && !hasMetadataVersion(skill.content)) {
|
|
5042
|
-
return injectMetadataVersion(skill.content, skill.skillVersion);
|
|
5043
|
-
}
|
|
5044
|
-
return skill.content;
|
|
5045
|
-
}
|
|
5046
|
-
function hasMetadataVersion(content) {
|
|
5047
|
-
return parseSkillVersion(content) !== null;
|
|
5048
|
-
}
|
|
5049
|
-
function injectMetadataVersion(content, version) {
|
|
5050
|
-
const fmMatch = content.match(/^(---\n[\s\S]*?\n)(---\n)([\s\S]*)$/);
|
|
5051
|
-
if (!fmMatch)
|
|
5052
|
-
return content;
|
|
5053
|
-
const [, head, close, body] = fmMatch;
|
|
5054
|
-
const block = `metadata:
|
|
5055
|
-
version: "${version}"
|
|
5056
|
-
`;
|
|
5057
|
-
return `${head}${block}${close}${body}`;
|
|
5058
|
-
}
|
|
5059
|
-
function parseSkillVersion(content) {
|
|
5060
|
-
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
5061
|
-
if (fmMatch) {
|
|
5062
|
-
const fm = fmMatch[1];
|
|
5063
|
-
const verMatch = fm.match(/^metadata:[\s\S]*?\n[ \t]+version:[ \t]*["']?(\d+)["']?\s*$/m);
|
|
5064
|
-
if (verMatch)
|
|
5065
|
-
return verMatch[1];
|
|
5066
|
-
}
|
|
5067
|
-
const legacy = content.match(/<!-- skills-version:(\d+) -->/);
|
|
5068
|
-
return legacy ? legacy[1] : null;
|
|
5069
|
-
}
|
|
5070
|
-
function findSkillFiles(paths, knownNames) {
|
|
5071
|
-
const results = [];
|
|
5072
|
-
for (const filePath of paths) {
|
|
5073
|
-
if (!existsSync3(filePath))
|
|
5074
|
-
continue;
|
|
5075
|
-
for (const name of knownNames) {
|
|
5076
|
-
if (filePath.includes(`/${name}/`) || filePath.includes(`/${name}.md`)) {
|
|
5077
|
-
results.push({ name, filePath });
|
|
5078
|
-
break;
|
|
5079
|
-
}
|
|
5080
|
-
}
|
|
5081
|
-
}
|
|
5082
|
-
return results;
|
|
5083
|
-
}
|
|
5084
|
-
async function refreshSkills() {
|
|
5085
|
-
try {
|
|
5086
|
-
if (!isConfigured())
|
|
5087
|
-
return;
|
|
5088
|
-
const status = areSkillsInstalled();
|
|
5089
|
-
if (!status.installed)
|
|
5090
|
-
return;
|
|
5091
|
-
const client3 = new HarmonyApiClient;
|
|
5092
|
-
const versionInfo = await client3.fetchSkillsVersion();
|
|
5093
|
-
const skillFiles = findSkillFiles(status.paths, versionInfo.skills);
|
|
5094
|
-
if (skillFiles.length > 0) {
|
|
5095
|
-
const samplePath = skillFiles[0].filePath;
|
|
5096
|
-
for (const name of versionInfo.skills) {
|
|
5097
|
-
if (skillFiles.some((sf) => sf.name === name))
|
|
5098
|
-
continue;
|
|
5099
|
-
let siblingPath;
|
|
5100
|
-
if (samplePath.endsWith("SKILL.md")) {
|
|
5101
|
-
const parentDir = dirname(dirname(samplePath));
|
|
5102
|
-
siblingPath = `${parentDir}/${name}/SKILL.md`;
|
|
5103
|
-
} else {
|
|
5104
|
-
const parentDir = dirname(samplePath);
|
|
5105
|
-
siblingPath = `${parentDir}/${name}.md`;
|
|
5106
|
-
}
|
|
5107
|
-
if (existsSync3(siblingPath)) {
|
|
5108
|
-
skillFiles.push({ name, filePath: siblingPath });
|
|
5109
|
-
}
|
|
5110
|
-
}
|
|
5111
|
-
}
|
|
5112
|
-
if (skillFiles.length === 0)
|
|
5113
|
-
return;
|
|
5114
|
-
let updated = false;
|
|
5115
|
-
for (const { name, filePath } of skillFiles) {
|
|
5116
|
-
try {
|
|
5117
|
-
const currentContent = readFileSync3(filePath, "utf-8");
|
|
5118
|
-
const localVersion = parseSkillVersion(currentContent);
|
|
5119
|
-
const fetched = await client3.fetchSkill(name);
|
|
5120
|
-
const remoteVersion = fetched.skillVersion;
|
|
5121
|
-
if (remoteVersion !== undefined && localVersion !== null && Number(localVersion) >= remoteVersion) {
|
|
5122
|
-
continue;
|
|
5123
|
-
}
|
|
5124
|
-
const newContent = buildSkillFile(fetched);
|
|
5125
|
-
const dir = dirname(filePath);
|
|
5126
|
-
if (!existsSync3(dir))
|
|
5127
|
-
mkdirSync3(dir, { recursive: true });
|
|
5128
|
-
writeFileSync3(filePath, newContent);
|
|
5129
|
-
updated = true;
|
|
5130
|
-
} catch (err) {
|
|
5131
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
5132
|
-
console.error(`Harmony: skill "${name}" refresh failed: ${msg}`);
|
|
5133
|
-
}
|
|
5134
|
-
}
|
|
5135
|
-
if (updated) {
|
|
5136
|
-
console.error("Harmony: Refreshed skills from server");
|
|
5137
|
-
}
|
|
5138
|
-
} catch {}
|
|
5139
|
-
}
|
|
5140
|
-
|
|
5141
5482
|
// src/tui/setup.ts
|
|
5142
5483
|
import { createHash as createHash3 } from "node:crypto";
|
|
5143
5484
|
import {
|
|
5144
|
-
existsSync as
|
|
5485
|
+
existsSync as existsSync8,
|
|
5145
5486
|
lstatSync,
|
|
5146
5487
|
mkdirSync as mkdirSync5,
|
|
5147
5488
|
symlinkSync,
|
|
5148
5489
|
unlinkSync
|
|
5149
5490
|
} from "node:fs";
|
|
5150
|
-
import { homedir as
|
|
5151
|
-
import { dirname as dirname3, join as
|
|
5491
|
+
import { homedir as homedir6 } from "node:os";
|
|
5492
|
+
import { dirname as dirname3, join as join7 } from "node:path";
|
|
5152
5493
|
import * as p3 from "@clack/prompts";
|
|
5153
5494
|
|
|
5154
5495
|
// src/tui/agents.ts
|
|
5155
|
-
import { existsSync as
|
|
5156
|
-
import { homedir as
|
|
5157
|
-
import { join as
|
|
5496
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
5497
|
+
import { homedir as homedir4 } from "node:os";
|
|
5498
|
+
import { join as join5 } from "node:path";
|
|
5158
5499
|
var AGENT_DEFINITIONS = [
|
|
5159
5500
|
{
|
|
5160
5501
|
id: "claude",
|
|
5161
5502
|
name: "Claude Code",
|
|
5162
5503
|
description: "Anthropic CLI agent",
|
|
5163
5504
|
hint: "/hmy <card>",
|
|
5164
|
-
globalPaths: [
|
|
5505
|
+
globalPaths: [join5(homedir4(), ".claude")],
|
|
5165
5506
|
localPaths: [".claude"]
|
|
5166
5507
|
},
|
|
5167
5508
|
{
|
|
@@ -5169,7 +5510,7 @@ var AGENT_DEFINITIONS = [
|
|
|
5169
5510
|
name: "Codex",
|
|
5170
5511
|
description: "OpenAI coding agent",
|
|
5171
5512
|
hint: "/prompts:hmy <card>",
|
|
5172
|
-
globalPaths: [
|
|
5513
|
+
globalPaths: [join5(homedir4(), ".codex")],
|
|
5173
5514
|
localPaths: ["AGENTS.md"]
|
|
5174
5515
|
},
|
|
5175
5516
|
{
|
|
@@ -5185,20 +5526,20 @@ var AGENT_DEFINITIONS = [
|
|
|
5185
5526
|
name: "Windsurf",
|
|
5186
5527
|
description: "Codeium AI IDE",
|
|
5187
5528
|
hint: "MCP tools available automatically",
|
|
5188
|
-
globalPaths: [
|
|
5529
|
+
globalPaths: [join5(homedir4(), ".codeium", "windsurf")],
|
|
5189
5530
|
localPaths: [".windsurf", ".windsurfrules"]
|
|
5190
5531
|
}
|
|
5191
5532
|
];
|
|
5192
5533
|
function detectAgents(cwd = process.cwd()) {
|
|
5193
5534
|
return AGENT_DEFINITIONS.map((def) => {
|
|
5194
|
-
const globalPath = def.globalPaths.find((p) =>
|
|
5195
|
-
const localPath = def.localPaths.find((p) =>
|
|
5535
|
+
const globalPath = def.globalPaths.find((p) => existsSync5(p)) || null;
|
|
5536
|
+
const localPath = def.localPaths.find((p) => existsSync5(join5(cwd, p))) || null;
|
|
5196
5537
|
return {
|
|
5197
5538
|
id: def.id,
|
|
5198
5539
|
name: def.name,
|
|
5199
5540
|
detected: !!(globalPath || localPath),
|
|
5200
5541
|
globalPath,
|
|
5201
|
-
localPath: localPath ?
|
|
5542
|
+
localPath: localPath ? join5(cwd, localPath) : null,
|
|
5202
5543
|
description: def.description,
|
|
5203
5544
|
hint: def.hint
|
|
5204
5545
|
};
|
|
@@ -5206,8 +5547,8 @@ function detectAgents(cwd = process.cwd()) {
|
|
|
5206
5547
|
}
|
|
5207
5548
|
|
|
5208
5549
|
// src/tui/docs.ts
|
|
5209
|
-
import { existsSync as
|
|
5210
|
-
import { isAbsolute, join as
|
|
5550
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync } from "node:fs";
|
|
5551
|
+
import { isAbsolute, join as join6, resolve, sep as sep2 } from "node:path";
|
|
5211
5552
|
import * as p from "@clack/prompts";
|
|
5212
5553
|
|
|
5213
5554
|
// src/tui/theme.ts
|
|
@@ -5294,14 +5635,14 @@ var IGNORED_DIRS = new Set([
|
|
|
5294
5635
|
]);
|
|
5295
5636
|
function readJson(filePath) {
|
|
5296
5637
|
try {
|
|
5297
|
-
return JSON.parse(
|
|
5638
|
+
return JSON.parse(readFileSync5(filePath, "utf-8"));
|
|
5298
5639
|
} catch {
|
|
5299
5640
|
return null;
|
|
5300
5641
|
}
|
|
5301
5642
|
}
|
|
5302
5643
|
function readText(filePath) {
|
|
5303
5644
|
try {
|
|
5304
|
-
return
|
|
5645
|
+
return readFileSync5(filePath, "utf-8");
|
|
5305
5646
|
} catch {
|
|
5306
5647
|
return null;
|
|
5307
5648
|
}
|
|
@@ -5312,7 +5653,7 @@ function listDirs(dirPath) {
|
|
|
5312
5653
|
if (IGNORED_DIRS.has(entry) || entry.startsWith("."))
|
|
5313
5654
|
return false;
|
|
5314
5655
|
try {
|
|
5315
|
-
return statSync(
|
|
5656
|
+
return statSync(join6(dirPath, entry)).isDirectory();
|
|
5316
5657
|
} catch {
|
|
5317
5658
|
return false;
|
|
5318
5659
|
}
|
|
@@ -5360,25 +5701,25 @@ function describeDir(name) {
|
|
|
5360
5701
|
}
|
|
5361
5702
|
function scanProject(cwd) {
|
|
5362
5703
|
let packageManager = null;
|
|
5363
|
-
if (
|
|
5704
|
+
if (existsSync6(join6(cwd, "bun.lock")) || existsSync6(join6(cwd, "bun.lockb"))) {
|
|
5364
5705
|
packageManager = "bun";
|
|
5365
|
-
} else if (
|
|
5706
|
+
} else if (existsSync6(join6(cwd, "pnpm-lock.yaml"))) {
|
|
5366
5707
|
packageManager = "pnpm";
|
|
5367
|
-
} else if (
|
|
5708
|
+
} else if (existsSync6(join6(cwd, "yarn.lock"))) {
|
|
5368
5709
|
packageManager = "yarn";
|
|
5369
|
-
} else if (
|
|
5710
|
+
} else if (existsSync6(join6(cwd, "package.json"))) {
|
|
5370
5711
|
packageManager = "npm";
|
|
5371
5712
|
}
|
|
5372
|
-
const pkg = readJson(
|
|
5713
|
+
const pkg = readJson(join6(cwd, "package.json"));
|
|
5373
5714
|
const scripts = pkg && typeof pkg.scripts === "object" && pkg.scripts !== null ? pkg.scripts : {};
|
|
5374
5715
|
let language = "unknown";
|
|
5375
|
-
if (
|
|
5716
|
+
if (existsSync6(join6(cwd, "tsconfig.json"))) {
|
|
5376
5717
|
language = "typescript";
|
|
5377
|
-
} else if (
|
|
5718
|
+
} else if (existsSync6(join6(cwd, "go.mod"))) {
|
|
5378
5719
|
language = "go";
|
|
5379
|
-
} else if (
|
|
5720
|
+
} else if (existsSync6(join6(cwd, "Cargo.toml"))) {
|
|
5380
5721
|
language = "rust";
|
|
5381
|
-
} else if (
|
|
5722
|
+
} else if (existsSync6(join6(cwd, "setup.py")) || existsSync6(join6(cwd, "pyproject.toml"))) {
|
|
5382
5723
|
language = "python";
|
|
5383
5724
|
} else if (pkg) {
|
|
5384
5725
|
language = "javascript";
|
|
@@ -5414,7 +5755,7 @@ function scanProject(cwd) {
|
|
|
5414
5755
|
}
|
|
5415
5756
|
}
|
|
5416
5757
|
let linter = null;
|
|
5417
|
-
if (
|
|
5758
|
+
if (existsSync6(join6(cwd, "biome.json")) || existsSync6(join6(cwd, "biome.jsonc"))) {
|
|
5418
5759
|
linter = "biome";
|
|
5419
5760
|
} else {
|
|
5420
5761
|
const eslintFiles = [
|
|
@@ -5429,7 +5770,7 @@ function scanProject(cwd) {
|
|
|
5429
5770
|
"eslint.config.cjs",
|
|
5430
5771
|
"eslint.config.ts"
|
|
5431
5772
|
];
|
|
5432
|
-
if (eslintFiles.some((f) =>
|
|
5773
|
+
if (eslintFiles.some((f) => existsSync6(join6(cwd, f)))) {
|
|
5433
5774
|
linter = "eslint";
|
|
5434
5775
|
} else {
|
|
5435
5776
|
const prettierFiles = [
|
|
@@ -5441,13 +5782,13 @@ function scanProject(cwd) {
|
|
|
5441
5782
|
"prettier.config.js",
|
|
5442
5783
|
"prettier.config.mjs"
|
|
5443
5784
|
];
|
|
5444
|
-
if (prettierFiles.some((f) =>
|
|
5785
|
+
if (prettierFiles.some((f) => existsSync6(join6(cwd, f)))) {
|
|
5445
5786
|
linter = "prettier";
|
|
5446
5787
|
}
|
|
5447
5788
|
}
|
|
5448
5789
|
}
|
|
5449
5790
|
let indentStyle = null;
|
|
5450
|
-
const biome = readJson(
|
|
5791
|
+
const biome = readJson(join6(cwd, "biome.json")) ?? readJson(join6(cwd, "biome.jsonc"));
|
|
5451
5792
|
if (biome) {
|
|
5452
5793
|
const formatter = biome.formatter;
|
|
5453
5794
|
if (formatter) {
|
|
@@ -5457,7 +5798,7 @@ function scanProject(cwd) {
|
|
|
5457
5798
|
}
|
|
5458
5799
|
}
|
|
5459
5800
|
if (!indentStyle) {
|
|
5460
|
-
const editorConfig = readText(
|
|
5801
|
+
const editorConfig = readText(join6(cwd, ".editorconfig"));
|
|
5461
5802
|
if (editorConfig) {
|
|
5462
5803
|
const styleMatch = editorConfig.match(/indent_style\s*=\s*(space|tab)/);
|
|
5463
5804
|
const sizeMatch = editorConfig.match(/indent_size\s*=\s*(\d+)/);
|
|
@@ -5470,13 +5811,13 @@ function scanProject(cwd) {
|
|
|
5470
5811
|
}
|
|
5471
5812
|
}
|
|
5472
5813
|
const dirs = listDirs(cwd);
|
|
5473
|
-
const srcDirs =
|
|
5474
|
-
const monorepo =
|
|
5814
|
+
const srcDirs = existsSync6(join6(cwd, "src")) ? listDirs(join6(cwd, "src")) : [];
|
|
5815
|
+
const monorepo = existsSync6(join6(cwd, "packages")) || existsSync6(join6(cwd, "apps"));
|
|
5475
5816
|
const existingDocs = {
|
|
5476
|
-
agentsMd:
|
|
5477
|
-
claudeMd:
|
|
5478
|
-
docsDir:
|
|
5479
|
-
architectureMd:
|
|
5817
|
+
agentsMd: existsSync6(join6(cwd, "AGENTS.md")),
|
|
5818
|
+
claudeMd: existsSync6(join6(cwd, "CLAUDE.md")),
|
|
5819
|
+
docsDir: existsSync6(join6(cwd, "docs")),
|
|
5820
|
+
architectureMd: existsSync6(join6(cwd, "docs", "architecture.md"))
|
|
5480
5821
|
};
|
|
5481
5822
|
return {
|
|
5482
5823
|
packageManager,
|
|
@@ -5627,9 +5968,9 @@ var VAGUE_STANDARDS = [
|
|
|
5627
5968
|
];
|
|
5628
5969
|
function verifyDocs(cwd) {
|
|
5629
5970
|
const issues = [];
|
|
5630
|
-
const claudeMd = readText(
|
|
5631
|
-
const agentsMd = readText(
|
|
5632
|
-
const pkg = readJson(
|
|
5971
|
+
const claudeMd = readText(join6(cwd, "CLAUDE.md"));
|
|
5972
|
+
const agentsMd = readText(join6(cwd, "AGENTS.md"));
|
|
5973
|
+
const pkg = readJson(join6(cwd, "package.json"));
|
|
5633
5974
|
const pkgScripts = pkg && typeof pkg.scripts === "object" && pkg.scripts !== null ? pkg.scripts : {};
|
|
5634
5975
|
const projectRoot = resolve(cwd);
|
|
5635
5976
|
if (claudeMd) {
|
|
@@ -5659,7 +6000,7 @@ function verifyDocs(cwd) {
|
|
|
5659
6000
|
continue;
|
|
5660
6001
|
}
|
|
5661
6002
|
importedFiles.push({ ref: refPath, resolved: resolvedPath });
|
|
5662
|
-
if (!
|
|
6003
|
+
if (!existsSync6(resolvedPath)) {
|
|
5663
6004
|
issues.push({
|
|
5664
6005
|
severity: "error",
|
|
5665
6006
|
file: "CLAUDE.md",
|
|
@@ -5795,7 +6136,7 @@ function verifyDocs(cwd) {
|
|
|
5795
6136
|
}
|
|
5796
6137
|
checkBacktickPaths(agentsMd, "AGENTS.md", cwd, issues);
|
|
5797
6138
|
}
|
|
5798
|
-
const archMd = readText(
|
|
6139
|
+
const archMd = readText(join6(cwd, "docs", "architecture.md"));
|
|
5799
6140
|
if (archMd) {
|
|
5800
6141
|
checkBacktickPaths(archMd, "docs/architecture.md", cwd, issues);
|
|
5801
6142
|
}
|
|
@@ -5842,7 +6183,7 @@ function checkBacktickPaths(content, file, cwd, issues) {
|
|
|
5842
6183
|
const resolvedRef = resolve(root, refPath);
|
|
5843
6184
|
if (resolvedRef !== root && !resolvedRef.startsWith(root + sep2))
|
|
5844
6185
|
continue;
|
|
5845
|
-
if (!
|
|
6186
|
+
if (!existsSync6(resolvedRef)) {
|
|
5846
6187
|
issues.push({
|
|
5847
6188
|
severity: "warning",
|
|
5848
6189
|
file,
|
|
@@ -5865,18 +6206,18 @@ async function runDocsStep(cwd) {
|
|
|
5865
6206
|
}
|
|
5866
6207
|
const files = [];
|
|
5867
6208
|
files.push({
|
|
5868
|
-
path:
|
|
6209
|
+
path: join6(cwd, "AGENTS.md"),
|
|
5869
6210
|
content: generateAgentsMd(info, cwd),
|
|
5870
6211
|
type: "text"
|
|
5871
6212
|
});
|
|
5872
6213
|
files.push({
|
|
5873
|
-
path:
|
|
6214
|
+
path: join6(cwd, "CLAUDE.md"),
|
|
5874
6215
|
content: generateClaudeMd(info),
|
|
5875
6216
|
type: "text"
|
|
5876
6217
|
});
|
|
5877
6218
|
if (info.dirs.includes("docs") || info.srcDirs.length > 0) {
|
|
5878
6219
|
files.push({
|
|
5879
|
-
path:
|
|
6220
|
+
path: join6(cwd, "docs", "architecture.md"),
|
|
5880
6221
|
content: generateArchitectureMd(info, cwd),
|
|
5881
6222
|
type: "text"
|
|
5882
6223
|
});
|
|
@@ -5914,21 +6255,21 @@ async function runDocsStep(cwd) {
|
|
|
5914
6255
|
// src/tui/writer.ts
|
|
5915
6256
|
import {
|
|
5916
6257
|
chmodSync,
|
|
5917
|
-
existsSync as
|
|
6258
|
+
existsSync as existsSync7,
|
|
5918
6259
|
mkdirSync as mkdirSync4,
|
|
5919
|
-
readFileSync as
|
|
6260
|
+
readFileSync as readFileSync6,
|
|
5920
6261
|
writeFileSync as writeFileSync4
|
|
5921
6262
|
} from "node:fs";
|
|
5922
|
-
import { homedir as
|
|
6263
|
+
import { homedir as homedir5 } from "node:os";
|
|
5923
6264
|
import { dirname as dirname2 } from "node:path";
|
|
5924
6265
|
import * as p2 from "@clack/prompts";
|
|
5925
6266
|
function ensureDir(dirPath) {
|
|
5926
|
-
if (!
|
|
6267
|
+
if (!existsSync7(dirPath)) {
|
|
5927
6268
|
mkdirSync4(dirPath, { recursive: true, mode: 493 });
|
|
5928
6269
|
}
|
|
5929
6270
|
}
|
|
5930
6271
|
function writeFile(filePath, content, options = {}) {
|
|
5931
|
-
const exists =
|
|
6272
|
+
const exists = existsSync7(filePath);
|
|
5932
6273
|
if (exists && !options.force) {
|
|
5933
6274
|
return { path: filePath, action: "skip" };
|
|
5934
6275
|
}
|
|
@@ -5950,7 +6291,7 @@ function writeFile(filePath, content, options = {}) {
|
|
|
5950
6291
|
}
|
|
5951
6292
|
}
|
|
5952
6293
|
function mergeJsonFile(filePath, updates, options = {}) {
|
|
5953
|
-
const exists =
|
|
6294
|
+
const exists = existsSync7(filePath);
|
|
5954
6295
|
if (!exists) {
|
|
5955
6296
|
try {
|
|
5956
6297
|
ensureDir(dirname2(filePath));
|
|
@@ -5967,7 +6308,7 @@ function mergeJsonFile(filePath, updates, options = {}) {
|
|
|
5967
6308
|
}
|
|
5968
6309
|
}
|
|
5969
6310
|
try {
|
|
5970
|
-
const existing = JSON.parse(
|
|
6311
|
+
const existing = JSON.parse(readFileSync6(filePath, "utf-8"));
|
|
5971
6312
|
if (updates.mcpServers && existing.mcpServers) {
|
|
5972
6313
|
const existingServers = existing.mcpServers;
|
|
5973
6314
|
const updateServers = updates.mcpServers;
|
|
@@ -6000,7 +6341,7 @@ function mergeJsonFile(filePath, updates, options = {}) {
|
|
|
6000
6341
|
}
|
|
6001
6342
|
}
|
|
6002
6343
|
function appendToToml(filePath, section, content, options = {}) {
|
|
6003
|
-
const exists =
|
|
6344
|
+
const exists = existsSync7(filePath);
|
|
6004
6345
|
if (!exists) {
|
|
6005
6346
|
try {
|
|
6006
6347
|
ensureDir(dirname2(filePath));
|
|
@@ -6015,7 +6356,7 @@ function appendToToml(filePath, section, content, options = {}) {
|
|
|
6015
6356
|
}
|
|
6016
6357
|
}
|
|
6017
6358
|
try {
|
|
6018
|
-
const existing =
|
|
6359
|
+
const existing = readFileSync6(filePath, "utf-8");
|
|
6019
6360
|
if (existing.includes(section)) {
|
|
6020
6361
|
if (options.force) {
|
|
6021
6362
|
const updated = existing.replace(new RegExp(`\\[${section.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\][\\s\\S]*?(?=\\[|$)`), content.trim() + `
|
|
@@ -6039,7 +6380,7 @@ function appendToToml(filePath, section, content, options = {}) {
|
|
|
6039
6380
|
}
|
|
6040
6381
|
async function writeFilesWithProgress(files, options = {}) {
|
|
6041
6382
|
const results = [];
|
|
6042
|
-
const home =
|
|
6383
|
+
const home = homedir5();
|
|
6043
6384
|
const spinner2 = p2.spinner();
|
|
6044
6385
|
spinner2.start("Writing configuration files...");
|
|
6045
6386
|
for (const file of files) {
|
|
@@ -6076,10 +6417,10 @@ function getWriteSummary(files, options = {}) {
|
|
|
6076
6417
|
const toCreate = [];
|
|
6077
6418
|
const toUpdate = [];
|
|
6078
6419
|
const toSkip = [];
|
|
6079
|
-
const home =
|
|
6420
|
+
const home = homedir5();
|
|
6080
6421
|
for (const file of files) {
|
|
6081
6422
|
const displayPath = formatPath(file.path, home);
|
|
6082
|
-
const exists =
|
|
6423
|
+
const exists = existsSync7(file.path);
|
|
6083
6424
|
if (exists && !options.force) {
|
|
6084
6425
|
toSkip.push(displayPath);
|
|
6085
6426
|
} else if (exists) {
|
|
@@ -6092,7 +6433,7 @@ function getWriteSummary(files, options = {}) {
|
|
|
6092
6433
|
}
|
|
6093
6434
|
|
|
6094
6435
|
// src/tui/setup.ts
|
|
6095
|
-
var GLOBAL_SKILLS_DIR =
|
|
6436
|
+
var GLOBAL_SKILLS_DIR = join7(homedir6(), ".agents", "skills");
|
|
6096
6437
|
var API_URL = "https://app.gethmy.com/api";
|
|
6097
6438
|
async function registerMcpServer() {
|
|
6098
6439
|
try {
|
|
@@ -6106,16 +6447,16 @@ async function registerMcpServer() {
|
|
|
6106
6447
|
}
|
|
6107
6448
|
}
|
|
6108
6449
|
async function writeMcpConfigFallback(home) {
|
|
6109
|
-
const { readFileSync:
|
|
6110
|
-
const settingsPath =
|
|
6450
|
+
const { readFileSync: readFileSync7, writeFileSync: writeFileSync5, mkdirSync: mkdirSync6, existsSync: existsSync9 } = await import("node:fs");
|
|
6451
|
+
const settingsPath = join7(home, ".claude", "settings.json");
|
|
6111
6452
|
const settingsDir = dirname3(settingsPath);
|
|
6112
|
-
if (!
|
|
6453
|
+
if (!existsSync9(settingsDir)) {
|
|
6113
6454
|
mkdirSync6(settingsDir, { recursive: true });
|
|
6114
6455
|
}
|
|
6115
6456
|
let settings = {};
|
|
6116
|
-
if (
|
|
6457
|
+
if (existsSync9(settingsPath)) {
|
|
6117
6458
|
try {
|
|
6118
|
-
settings = JSON.parse(
|
|
6459
|
+
settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
|
|
6119
6460
|
} catch {}
|
|
6120
6461
|
}
|
|
6121
6462
|
const mcpServers = settings.mcpServers || {};
|
|
@@ -6206,7 +6547,7 @@ async function resolveProjectSlug(apiKey, slug) {
|
|
|
6206
6547
|
return { workspaceId: data.workspaceId, projectId: data.projectId };
|
|
6207
6548
|
}
|
|
6208
6549
|
async function getAgentFiles(agentId, cwd, installMode = "global") {
|
|
6209
|
-
const home =
|
|
6550
|
+
const home = homedir6();
|
|
6210
6551
|
const files = [];
|
|
6211
6552
|
const symlinks = [];
|
|
6212
6553
|
switch (agentId) {
|
|
@@ -6221,17 +6562,17 @@ async function getAgentFiles(agentId, cwd, installMode = "global") {
|
|
|
6221
6562
|
const content = buildSkillFile(fetched);
|
|
6222
6563
|
if (installMode === "global") {
|
|
6223
6564
|
files.push({
|
|
6224
|
-
path:
|
|
6565
|
+
path: join7(GLOBAL_SKILLS_DIR, name, "SKILL.md"),
|
|
6225
6566
|
content,
|
|
6226
6567
|
type: "text"
|
|
6227
6568
|
});
|
|
6228
6569
|
symlinks.push({
|
|
6229
|
-
target:
|
|
6230
|
-
link:
|
|
6570
|
+
target: join7(GLOBAL_SKILLS_DIR, name),
|
|
6571
|
+
link: join7(home, ".claude", "skills", name)
|
|
6231
6572
|
});
|
|
6232
6573
|
} else {
|
|
6233
6574
|
files.push({
|
|
6234
|
-
path:
|
|
6575
|
+
path: join7(cwd, ".claude", "skills", name, "SKILL.md"),
|
|
6235
6576
|
content,
|
|
6236
6577
|
type: "text"
|
|
6237
6578
|
});
|
|
@@ -6254,13 +6595,13 @@ ${summary}`);
|
|
|
6254
6595
|
throw new Error(`hmy-update-check integrity check failed: expected ${updateCheckFetched.sha256}, got ${actualHash}`);
|
|
6255
6596
|
}
|
|
6256
6597
|
files.push({
|
|
6257
|
-
path:
|
|
6598
|
+
path: join7(home, ".hmy", "bin", "hmy-update-check"),
|
|
6258
6599
|
content: updateCheckFetched.content,
|
|
6259
6600
|
type: "text",
|
|
6260
6601
|
mode: 493
|
|
6261
6602
|
});
|
|
6262
6603
|
files.push({
|
|
6263
|
-
path:
|
|
6604
|
+
path: join7(home, ".hmy", "VERSION"),
|
|
6264
6605
|
content: versionInfo.version,
|
|
6265
6606
|
type: "text"
|
|
6266
6607
|
});
|
|
@@ -6306,7 +6647,7 @@ Skip if: work was already started with a card reference, or no matching card exi
|
|
|
6306
6647
|
- \`harmony_generate_prompt\` - Get role-based guidance and focus areas for the card
|
|
6307
6648
|
`;
|
|
6308
6649
|
files.push({
|
|
6309
|
-
path:
|
|
6650
|
+
path: join7(cwd, "AGENTS.md"),
|
|
6310
6651
|
content: agentsContent,
|
|
6311
6652
|
type: "text"
|
|
6312
6653
|
});
|
|
@@ -6323,17 +6664,17 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "{{card}}").replace("Your agent
|
|
|
6323
6664
|
`;
|
|
6324
6665
|
if (installMode === "global") {
|
|
6325
6666
|
files.push({
|
|
6326
|
-
path:
|
|
6667
|
+
path: join7(GLOBAL_SKILLS_DIR, "codex", "hmy.md"),
|
|
6327
6668
|
content: promptContent,
|
|
6328
6669
|
type: "text"
|
|
6329
6670
|
});
|
|
6330
6671
|
symlinks.push({
|
|
6331
|
-
target:
|
|
6332
|
-
link:
|
|
6672
|
+
target: join7(GLOBAL_SKILLS_DIR, "codex", "hmy.md"),
|
|
6673
|
+
link: join7(home, ".codex", "prompts", "hmy.md")
|
|
6333
6674
|
});
|
|
6334
6675
|
} else {
|
|
6335
6676
|
files.push({
|
|
6336
|
-
path:
|
|
6677
|
+
path: join7(home, ".codex", "prompts", "hmy.md"),
|
|
6337
6678
|
content: promptContent,
|
|
6338
6679
|
type: "text"
|
|
6339
6680
|
});
|
|
@@ -6345,7 +6686,7 @@ command = "npx"
|
|
|
6345
6686
|
args = ["-y", "@gethmy/mcp@latest", "serve"]
|
|
6346
6687
|
`;
|
|
6347
6688
|
files.push({
|
|
6348
|
-
path:
|
|
6689
|
+
path: join7(home, ".codex", "config.toml"),
|
|
6349
6690
|
content: tomlContent,
|
|
6350
6691
|
type: "toml",
|
|
6351
6692
|
tomlSection: "mcp_servers.harmony"
|
|
@@ -6354,7 +6695,7 @@ args = ["-y", "@gethmy/mcp@latest", "serve"]
|
|
|
6354
6695
|
}
|
|
6355
6696
|
case "cursor": {
|
|
6356
6697
|
files.push({
|
|
6357
|
-
path:
|
|
6698
|
+
path: join7(cwd, ".cursor", "mcp.json"),
|
|
6358
6699
|
content: JSON.stringify({
|
|
6359
6700
|
mcpServers: {
|
|
6360
6701
|
harmony: {
|
|
@@ -6380,17 +6721,17 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Y
|
|
|
6380
6721
|
`;
|
|
6381
6722
|
if (installMode === "global") {
|
|
6382
6723
|
files.push({
|
|
6383
|
-
path:
|
|
6724
|
+
path: join7(GLOBAL_SKILLS_DIR, "cursor", "harmony.mdc"),
|
|
6384
6725
|
content: ruleContent,
|
|
6385
6726
|
type: "text"
|
|
6386
6727
|
});
|
|
6387
6728
|
symlinks.push({
|
|
6388
|
-
target:
|
|
6389
|
-
link:
|
|
6729
|
+
target: join7(GLOBAL_SKILLS_DIR, "cursor", "harmony.mdc"),
|
|
6730
|
+
link: join7(home, ".cursor", "rules", "harmony.mdc")
|
|
6390
6731
|
});
|
|
6391
6732
|
} else {
|
|
6392
6733
|
files.push({
|
|
6393
|
-
path:
|
|
6734
|
+
path: join7(cwd, ".cursor", "rules", "harmony.mdc"),
|
|
6394
6735
|
content: ruleContent,
|
|
6395
6736
|
type: "text"
|
|
6396
6737
|
});
|
|
@@ -6399,7 +6740,7 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Y
|
|
|
6399
6740
|
}
|
|
6400
6741
|
case "windsurf": {
|
|
6401
6742
|
files.push({
|
|
6402
|
-
path:
|
|
6743
|
+
path: join7(home, ".codeium", "windsurf", "mcp_config.json"),
|
|
6403
6744
|
content: JSON.stringify({
|
|
6404
6745
|
mcpServers: {
|
|
6405
6746
|
harmony: {
|
|
@@ -6425,17 +6766,17 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Y
|
|
|
6425
6766
|
`;
|
|
6426
6767
|
if (installMode === "global") {
|
|
6427
6768
|
files.push({
|
|
6428
|
-
path:
|
|
6769
|
+
path: join7(GLOBAL_SKILLS_DIR, "windsurf", "harmony.md"),
|
|
6429
6770
|
content: ruleContent,
|
|
6430
6771
|
type: "text"
|
|
6431
6772
|
});
|
|
6432
6773
|
symlinks.push({
|
|
6433
|
-
target:
|
|
6434
|
-
link:
|
|
6774
|
+
target: join7(GLOBAL_SKILLS_DIR, "windsurf", "harmony.md"),
|
|
6775
|
+
link: join7(home, ".codeium", "windsurf", "rules", "harmony.md")
|
|
6435
6776
|
});
|
|
6436
6777
|
} else {
|
|
6437
6778
|
files.push({
|
|
6438
|
-
path:
|
|
6779
|
+
path: join7(cwd, ".windsurf", "rules", "harmony.md"),
|
|
6439
6780
|
content: ruleContent,
|
|
6440
6781
|
type: "text"
|
|
6441
6782
|
});
|
|
@@ -6447,7 +6788,7 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Y
|
|
|
6447
6788
|
}
|
|
6448
6789
|
async function runSetup(options = {}) {
|
|
6449
6790
|
const cwd = process.cwd();
|
|
6450
|
-
const home =
|
|
6791
|
+
const home = homedir6();
|
|
6451
6792
|
console.clear();
|
|
6452
6793
|
console.log(messages.header());
|
|
6453
6794
|
const existingConfig = loadConfig();
|
|
@@ -6855,7 +7196,7 @@ async function runSetup(options = {}) {
|
|
|
6855
7196
|
for (const symlink of allSymlinks) {
|
|
6856
7197
|
try {
|
|
6857
7198
|
const linkDir = dirname3(symlink.link);
|
|
6858
|
-
if (!
|
|
7199
|
+
if (!existsSync8(linkDir)) {
|
|
6859
7200
|
mkdirSync5(linkDir, { recursive: true });
|
|
6860
7201
|
}
|
|
6861
7202
|
let linkExists = false;
|
|
@@ -6884,7 +7225,7 @@ async function runSetup(options = {}) {
|
|
|
6884
7225
|
} else {
|
|
6885
7226
|
try {
|
|
6886
7227
|
await writeMcpConfigFallback(home);
|
|
6887
|
-
console.log(` ${colors.success("✓")} ${colors.dim(formatPath(
|
|
7228
|
+
console.log(` ${colors.success("✓")} ${colors.dim(formatPath(join7(home, ".claude", "settings.json"), home))} ${colors.dim("(updated)")}`);
|
|
6888
7229
|
} catch {
|
|
6889
7230
|
p3.log.warning("Could not register MCP server. Run manually: claude mcp add --transport stdio harmony -- npx -y @gethmy/mcp@latest serve");
|
|
6890
7231
|
}
|
|
@@ -6951,9 +7292,9 @@ program.command("serve").description("Start the MCP server (stdio transport)").a
|
|
|
6951
7292
|
console.error("Run: npx @gethmy/mcp setup");
|
|
6952
7293
|
process.exit(1);
|
|
6953
7294
|
}
|
|
6954
|
-
await refreshSkills();
|
|
7295
|
+
const { updated } = await refreshSkills();
|
|
6955
7296
|
const server = new HarmonyMCPServer;
|
|
6956
|
-
await server.run();
|
|
7297
|
+
await server.run({ skillsUpdated: updated });
|
|
6957
7298
|
});
|
|
6958
7299
|
program.command("status").description("Show configuration status").action(() => {
|
|
6959
7300
|
const globalConfig = loadConfig();
|