harmony-mcp 1.6.0 → 1.6.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/package.json +1 -1
- package/dist/init.js +0 -624
package/package.json
CHANGED
package/dist/init.js
DELETED
|
@@ -1,624 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
-
for (let key of __getOwnPropNames(mod))
|
|
11
|
-
if (!__hasOwnProp.call(to, key))
|
|
12
|
-
__defProp(to, key, {
|
|
13
|
-
get: () => mod[key],
|
|
14
|
-
enumerable: true
|
|
15
|
-
});
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
19
|
-
var __export = (target, all) => {
|
|
20
|
-
for (var name in all)
|
|
21
|
-
__defProp(target, name, {
|
|
22
|
-
get: all[name],
|
|
23
|
-
enumerable: true,
|
|
24
|
-
configurable: true,
|
|
25
|
-
set: (newValue) => all[name] = () => newValue
|
|
26
|
-
});
|
|
27
|
-
};
|
|
28
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
29
|
-
|
|
30
|
-
// src/init.ts
|
|
31
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync, symlinkSync, unlinkSync, lstatSync } from "node:fs";
|
|
32
|
-
import { join, dirname } from "node:path";
|
|
33
|
-
import { homedir } from "node:os";
|
|
34
|
-
var HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
|
|
35
|
-
|
|
36
|
-
Start work on a Harmony card. Card reference: $ARGUMENTS
|
|
37
|
-
|
|
38
|
-
## 1. Find & Fetch Card
|
|
39
|
-
|
|
40
|
-
Parse the reference and fetch the card:
|
|
41
|
-
- \`#42\` or \`42\` → \`harmony_get_card_by_short_id\` with \`shortId: 42\`
|
|
42
|
-
- UUID → \`harmony_get_card\` with \`cardId\`
|
|
43
|
-
- Name/text → \`harmony_search_cards\` with \`query\`
|
|
44
|
-
|
|
45
|
-
## 2. Get Board State
|
|
46
|
-
|
|
47
|
-
Call \`harmony_get_board\` to get columns and labels. From the response:
|
|
48
|
-
- Find the "In Progress" (or "Progress") column ID
|
|
49
|
-
- Find the "agent" label ID
|
|
50
|
-
|
|
51
|
-
## 3. Setup Card for Work
|
|
52
|
-
|
|
53
|
-
Execute these in sequence:
|
|
54
|
-
1. \`harmony_move_card\` → Move to "In Progress" column
|
|
55
|
-
2. \`harmony_add_label_to_card\` → Add "agent" label
|
|
56
|
-
3. \`harmony_start_agent_session\`:
|
|
57
|
-
- \`cardId\`: Card UUID
|
|
58
|
-
- \`agentIdentifier\`: Your agent identifier
|
|
59
|
-
- \`agentName\`: Your agent name
|
|
60
|
-
- \`currentTask\`: "Analyzing card requirements"
|
|
61
|
-
|
|
62
|
-
## 4. Generate Work Prompt
|
|
63
|
-
|
|
64
|
-
Call \`harmony_generate_prompt\` with:
|
|
65
|
-
- \`cardId\` or \`shortId\` (+ \`projectId\` if using shortId)
|
|
66
|
-
- \`variant\`: Select based on task:
|
|
67
|
-
- \`"execute"\` (default) → Clear tasks, bug fixes, well-defined work
|
|
68
|
-
- \`"analysis"\` → Complex features, unclear requirements
|
|
69
|
-
- \`"draft"\` → Medium complexity, want feedback first
|
|
70
|
-
|
|
71
|
-
The generated prompt provides role framing, focus areas, subtasks, linked cards, and suggested outputs.
|
|
72
|
-
|
|
73
|
-
## 5. Display Card Summary
|
|
74
|
-
|
|
75
|
-
Show the user: Card title, short ID, role, priority, labels, due date, description, and subtasks.
|
|
76
|
-
|
|
77
|
-
## 6. Implement Solution
|
|
78
|
-
|
|
79
|
-
Work on the card following the generated prompt's guidance. Update progress at milestones:
|
|
80
|
-
- \`harmony_update_agent_progress\` with \`progressPercent\` (0-100), \`currentTask\`, \`status\`, \`blockers\`
|
|
81
|
-
|
|
82
|
-
**Progress checkpoints:** 20% (exploration), 50% (implementation), 80% (testing), 100% (done)
|
|
83
|
-
|
|
84
|
-
## 7. Complete Work
|
|
85
|
-
|
|
86
|
-
When finished:
|
|
87
|
-
1. \`harmony_end_agent_session\` with \`status: "completed"\`, \`progressPercent: 100\`
|
|
88
|
-
2. \`harmony_move_card\` to "Review" column
|
|
89
|
-
3. Summarize accomplishments
|
|
90
|
-
|
|
91
|
-
If pausing: \`harmony_end_agent_session\` with \`status: "paused"\`
|
|
92
|
-
|
|
93
|
-
## Key Tools Reference
|
|
94
|
-
|
|
95
|
-
**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\`
|
|
96
|
-
|
|
97
|
-
**Subtasks:** \`harmony_create_subtask\`, \`harmony_toggle_subtask\`, \`harmony_delete_subtask\`
|
|
98
|
-
|
|
99
|
-
**Labels:** \`harmony_add_label_to_card\`, \`harmony_remove_label_from_card\`, \`harmony_create_label\`
|
|
100
|
-
|
|
101
|
-
**Links:** \`harmony_add_link_to_card\`, \`harmony_remove_link_from_card\`, \`harmony_get_card_links\`
|
|
102
|
-
|
|
103
|
-
**Board:** \`harmony_get_board\`, \`harmony_list_projects\`, \`harmony_get_context\`, \`harmony_set_project_context\`
|
|
104
|
-
|
|
105
|
-
**Sessions:** \`harmony_start_agent_session\`, \`harmony_update_agent_progress\`, \`harmony_end_agent_session\`, \`harmony_get_agent_session\`
|
|
106
|
-
|
|
107
|
-
**AI:** \`harmony_generate_prompt\`, \`harmony_process_command\`
|
|
108
|
-
`;
|
|
109
|
-
var HARMONY_STANDUP_PROMPT = `# Harmony Daily Standup
|
|
110
|
-
|
|
111
|
-
Generate a daily standup summary for the current project.
|
|
112
|
-
|
|
113
|
-
## 1. Get Board State
|
|
114
|
-
|
|
115
|
-
Call \`harmony_get_board\` to get the full board state including:
|
|
116
|
-
- All columns and their cards
|
|
117
|
-
- Card priorities, assignees, and due dates
|
|
118
|
-
- Active agent sessions
|
|
119
|
-
|
|
120
|
-
## 2. Analyze Board
|
|
121
|
-
|
|
122
|
-
Organize the information into standup categories:
|
|
123
|
-
|
|
124
|
-
### What was completed recently
|
|
125
|
-
- Cards in "Done" or "Review" columns
|
|
126
|
-
- Cards with recent activity (moved, updated)
|
|
127
|
-
|
|
128
|
-
### What's in progress
|
|
129
|
-
- Cards in "In Progress" column
|
|
130
|
-
- Cards with active agent sessions (show progress %)
|
|
131
|
-
- Who's working on what
|
|
132
|
-
|
|
133
|
-
### What's blocked or at risk
|
|
134
|
-
- High-priority cards not in progress
|
|
135
|
-
- Overdue cards
|
|
136
|
-
- Cards with blockers
|
|
137
|
-
|
|
138
|
-
### What's coming up
|
|
139
|
-
- Cards in "To Do" column
|
|
140
|
-
- Upcoming due dates
|
|
141
|
-
|
|
142
|
-
## 3. Present Summary
|
|
143
|
-
|
|
144
|
-
Format the summary as a clean, readable standup report:
|
|
145
|
-
- Use bullet points for easy scanning
|
|
146
|
-
- Highlight priorities and blockers
|
|
147
|
-
- Include card short IDs for easy reference (e.g., #42)
|
|
148
|
-
- Note any agent work in progress
|
|
149
|
-
`;
|
|
150
|
-
var HARMONY_CLEANUP_PROMPT = `# Harmony Board Cleanup
|
|
151
|
-
|
|
152
|
-
Analyze the board and suggest cleanup actions.
|
|
153
|
-
|
|
154
|
-
## 1. Get Board State
|
|
155
|
-
|
|
156
|
-
Call \`harmony_get_board\` to get the full board state.
|
|
157
|
-
|
|
158
|
-
## 2. Identify Issues
|
|
159
|
-
|
|
160
|
-
Look for:
|
|
161
|
-
|
|
162
|
-
### Stale cards
|
|
163
|
-
- Cards in "In Progress" for too long without updates
|
|
164
|
-
- Cards with past due dates
|
|
165
|
-
- Cards with no recent activity
|
|
166
|
-
|
|
167
|
-
### Organizational issues
|
|
168
|
-
- Cards missing priorities
|
|
169
|
-
- Cards missing assignees in active columns
|
|
170
|
-
- Empty descriptions on complex cards
|
|
171
|
-
|
|
172
|
-
### Potential duplicates
|
|
173
|
-
- Cards with similar titles
|
|
174
|
-
- Use \`harmony_search_cards\` if needed to find related cards
|
|
175
|
-
|
|
176
|
-
## 3. Suggest Actions
|
|
177
|
-
|
|
178
|
-
For each issue found, suggest a specific action:
|
|
179
|
-
- Move stale cards back to backlog
|
|
180
|
-
- Archive completed cards
|
|
181
|
-
- Update missing information
|
|
182
|
-
- Merge or link duplicates
|
|
183
|
-
|
|
184
|
-
Present suggestions as a prioritized list with:
|
|
185
|
-
- Card reference (#ID)
|
|
186
|
-
- Current state
|
|
187
|
-
- Suggested action
|
|
188
|
-
- Why it matters
|
|
189
|
-
|
|
190
|
-
## 4. Optional: Execute Cleanup
|
|
191
|
-
|
|
192
|
-
If the user approves, execute the suggested actions:
|
|
193
|
-
- Use \`harmony_move_card\` to reorganize
|
|
194
|
-
- Use \`harmony_update_card\` to add missing info
|
|
195
|
-
- Use \`harmony_add_link_to_card\` to link related cards
|
|
196
|
-
`;
|
|
197
|
-
function ensureDir(dirPath) {
|
|
198
|
-
if (!existsSync(dirPath)) {
|
|
199
|
-
mkdirSync(dirPath, { recursive: true });
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
function writeFileIfNotExists(filePath, content, force) {
|
|
203
|
-
if (existsSync(filePath) && !force) {
|
|
204
|
-
return { created: false, skipped: true };
|
|
205
|
-
}
|
|
206
|
-
ensureDir(dirname(filePath));
|
|
207
|
-
writeFileSync(filePath, content, "utf-8");
|
|
208
|
-
return { created: true, skipped: false };
|
|
209
|
-
}
|
|
210
|
-
var GLOBAL_SKILLS_DIR = join(homedir(), ".agents", "skills");
|
|
211
|
-
function pathExists(filePath) {
|
|
212
|
-
try {
|
|
213
|
-
lstatSync(filePath);
|
|
214
|
-
return true;
|
|
215
|
-
} catch {
|
|
216
|
-
return false;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
function createSymlink(target, linkPath, force) {
|
|
220
|
-
if (pathExists(linkPath)) {
|
|
221
|
-
if (!force) {
|
|
222
|
-
return { created: false, skipped: true };
|
|
223
|
-
}
|
|
224
|
-
try {
|
|
225
|
-
unlinkSync(linkPath);
|
|
226
|
-
} catch {}
|
|
227
|
-
}
|
|
228
|
-
ensureDir(dirname(linkPath));
|
|
229
|
-
symlinkSync(target, linkPath);
|
|
230
|
-
return { created: true, skipped: false };
|
|
231
|
-
}
|
|
232
|
-
function mergeJsonFile(filePath, updates, force) {
|
|
233
|
-
if (!existsSync(filePath)) {
|
|
234
|
-
ensureDir(dirname(filePath));
|
|
235
|
-
writeFileSync(filePath, JSON.stringify(updates, null, 2), "utf-8");
|
|
236
|
-
return { created: true, skipped: false, merged: false };
|
|
237
|
-
}
|
|
238
|
-
try {
|
|
239
|
-
const existing = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
240
|
-
if (updates.mcpServers && existing.mcpServers) {
|
|
241
|
-
if (existing.mcpServers.harmony && !force) {
|
|
242
|
-
return { created: false, skipped: true, merged: false };
|
|
243
|
-
}
|
|
244
|
-
existing.mcpServers = { ...existing.mcpServers, ...updates.mcpServers };
|
|
245
|
-
} else {
|
|
246
|
-
Object.assign(existing, updates);
|
|
247
|
-
}
|
|
248
|
-
writeFileSync(filePath, JSON.stringify(existing, null, 2), "utf-8");
|
|
249
|
-
return { created: false, skipped: false, merged: true };
|
|
250
|
-
} catch {
|
|
251
|
-
if (force) {
|
|
252
|
-
writeFileSync(filePath, JSON.stringify(updates, null, 2), "utf-8");
|
|
253
|
-
return { created: true, skipped: false, merged: false };
|
|
254
|
-
}
|
|
255
|
-
return { created: false, skipped: true, merged: false };
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
function initClaude(cwd, force, installMode) {
|
|
259
|
-
const result = {
|
|
260
|
-
agent: "claude",
|
|
261
|
-
filesCreated: [],
|
|
262
|
-
filesSkipped: [],
|
|
263
|
-
symlinksCreated: []
|
|
264
|
-
};
|
|
265
|
-
const skillContent = `---
|
|
266
|
-
name: hmy
|
|
267
|
-
description: Start working on a Harmony card. Use when given a card reference like #42, UUID, or card name to implement.
|
|
268
|
-
argument-hint: <card-reference>
|
|
269
|
-
---
|
|
270
|
-
|
|
271
|
-
${HARMONY_WORKFLOW_PROMPT.replace("Your agent identifier", "claude-code").replace("Your agent name", "Claude Code")}
|
|
272
|
-
`;
|
|
273
|
-
const standupContent = `---
|
|
274
|
-
name: hmy-standup
|
|
275
|
-
description: Generate a daily standup summary. Use when asked for project status, daily update, or standup report.
|
|
276
|
-
---
|
|
277
|
-
|
|
278
|
-
${HARMONY_STANDUP_PROMPT}
|
|
279
|
-
`;
|
|
280
|
-
const cleanupContent = `---
|
|
281
|
-
name: hmy-cleanup
|
|
282
|
-
description: Analyze board for stale cards and suggest cleanup. Use when asked to clean up, audit, or organize the board.
|
|
283
|
-
---
|
|
284
|
-
|
|
285
|
-
${HARMONY_CLEANUP_PROMPT}
|
|
286
|
-
`;
|
|
287
|
-
if (installMode === "global") {
|
|
288
|
-
const skills = [
|
|
289
|
-
{ name: "hmy", content: skillContent },
|
|
290
|
-
{ name: "hmy-standup", content: standupContent },
|
|
291
|
-
{ name: "hmy-cleanup", content: cleanupContent }
|
|
292
|
-
];
|
|
293
|
-
for (const skill of skills) {
|
|
294
|
-
const centralPath = join(GLOBAL_SKILLS_DIR, skill.name, "SKILL.md");
|
|
295
|
-
const { created, skipped } = writeFileIfNotExists(centralPath, skill.content, force);
|
|
296
|
-
if (created)
|
|
297
|
-
result.filesCreated.push(centralPath);
|
|
298
|
-
if (skipped)
|
|
299
|
-
result.filesSkipped.push(centralPath);
|
|
300
|
-
const symlinkPath = join(homedir(), ".claude", "skills", skill.name);
|
|
301
|
-
const symlinkTarget = join(GLOBAL_SKILLS_DIR, skill.name);
|
|
302
|
-
const symlinkResult = createSymlink(symlinkTarget, symlinkPath, force);
|
|
303
|
-
if (symlinkResult.created)
|
|
304
|
-
result.symlinksCreated.push(`${symlinkPath} → ${symlinkTarget}`);
|
|
305
|
-
if (symlinkResult.skipped)
|
|
306
|
-
result.filesSkipped.push(symlinkPath);
|
|
307
|
-
}
|
|
308
|
-
} else {
|
|
309
|
-
const skillPath = join(cwd, ".claude", "skills", "hmy", "SKILL.md");
|
|
310
|
-
const { created, skipped } = writeFileIfNotExists(skillPath, skillContent, force);
|
|
311
|
-
if (created)
|
|
312
|
-
result.filesCreated.push(skillPath);
|
|
313
|
-
if (skipped)
|
|
314
|
-
result.filesSkipped.push(skillPath);
|
|
315
|
-
const standupPath = join(cwd, ".claude", "skills", "hmy-standup", "SKILL.md");
|
|
316
|
-
const standupResult = writeFileIfNotExists(standupPath, standupContent, force);
|
|
317
|
-
if (standupResult.created)
|
|
318
|
-
result.filesCreated.push(standupPath);
|
|
319
|
-
if (standupResult.skipped)
|
|
320
|
-
result.filesSkipped.push(standupPath);
|
|
321
|
-
const cleanupPath = join(cwd, ".claude", "skills", "hmy-cleanup", "SKILL.md");
|
|
322
|
-
const cleanupResult = writeFileIfNotExists(cleanupPath, cleanupContent, force);
|
|
323
|
-
if (cleanupResult.created)
|
|
324
|
-
result.filesCreated.push(cleanupPath);
|
|
325
|
-
if (cleanupResult.skipped)
|
|
326
|
-
result.filesSkipped.push(cleanupPath);
|
|
327
|
-
}
|
|
328
|
-
const globalConfigPath = join(homedir(), ".claude", "settings.json");
|
|
329
|
-
const mcpConfig = {
|
|
330
|
-
mcpServers: {
|
|
331
|
-
harmony: {
|
|
332
|
-
command: "harmony-mcp",
|
|
333
|
-
args: ["serve"]
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
};
|
|
337
|
-
const configResult = mergeJsonFile(globalConfigPath, mcpConfig, force);
|
|
338
|
-
if (configResult.created || configResult.merged) {
|
|
339
|
-
result.filesCreated.push(globalConfigPath);
|
|
340
|
-
} else if (configResult.skipped) {
|
|
341
|
-
result.filesSkipped.push(globalConfigPath);
|
|
342
|
-
}
|
|
343
|
-
return result;
|
|
344
|
-
}
|
|
345
|
-
function initCodex(cwd, force, installMode) {
|
|
346
|
-
const result = {
|
|
347
|
-
agent: "codex",
|
|
348
|
-
filesCreated: [],
|
|
349
|
-
filesSkipped: [],
|
|
350
|
-
symlinksCreated: []
|
|
351
|
-
};
|
|
352
|
-
const agentsContent = `# Harmony Integration
|
|
353
|
-
|
|
354
|
-
This project uses Harmony for task management. When working on tasks:
|
|
355
|
-
|
|
356
|
-
## Starting Work on a Card
|
|
357
|
-
|
|
358
|
-
When given a card reference (e.g., #42 or a card name), follow this workflow:
|
|
359
|
-
|
|
360
|
-
1. Use \`harmony_get_card_by_short_id\` or \`harmony_search_cards\` to find the card
|
|
361
|
-
2. Move the card to "In Progress" using \`harmony_move_card\`
|
|
362
|
-
3. Add the "agent" label using \`harmony_add_label_to_card\`
|
|
363
|
-
4. Start a session with \`harmony_start_agent_session\` (agentIdentifier: "codex", agentName: "OpenAI Codex")
|
|
364
|
-
5. Show the card details to the user
|
|
365
|
-
6. Use \`harmony_generate_prompt\` to get guidance, then implement the solution
|
|
366
|
-
7. Update progress periodically with \`harmony_update_agent_progress\`
|
|
367
|
-
8. When done, call \`harmony_end_agent_session\` and move to "Review"
|
|
368
|
-
|
|
369
|
-
## Available Harmony Tools
|
|
370
|
-
|
|
371
|
-
- \`harmony_get_card\`, \`harmony_get_card_by_short_id\`, \`harmony_search_cards\` - Find cards
|
|
372
|
-
- \`harmony_move_card\` - Move cards between columns
|
|
373
|
-
- \`harmony_add_label_to_card\`, \`harmony_remove_label_from_card\` - Manage labels
|
|
374
|
-
- \`harmony_start_agent_session\`, \`harmony_update_agent_progress\`, \`harmony_end_agent_session\` - Track work
|
|
375
|
-
- \`harmony_get_board\` - Get board state
|
|
376
|
-
- \`harmony_generate_prompt\` - Get role-based guidance and focus areas for the card
|
|
377
|
-
`;
|
|
378
|
-
const promptContent = `---
|
|
379
|
-
name: hmy
|
|
380
|
-
description: Start working on a Harmony card
|
|
381
|
-
arguments:
|
|
382
|
-
- name: card
|
|
383
|
-
description: Card reference (#42, UUID, or name)
|
|
384
|
-
required: true
|
|
385
|
-
---
|
|
386
|
-
|
|
387
|
-
${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "{{card}}").replace("Your agent identifier", "codex").replace("Your agent name", "OpenAI Codex")}
|
|
388
|
-
`;
|
|
389
|
-
if (installMode === "global") {
|
|
390
|
-
const centralPromptPath = join(GLOBAL_SKILLS_DIR, "codex", "hmy.md");
|
|
391
|
-
const { created, skipped } = writeFileIfNotExists(centralPromptPath, promptContent, force);
|
|
392
|
-
if (created)
|
|
393
|
-
result.filesCreated.push(centralPromptPath);
|
|
394
|
-
if (skipped)
|
|
395
|
-
result.filesSkipped.push(centralPromptPath);
|
|
396
|
-
const symlinkPath = join(homedir(), ".codex", "prompts", "hmy.md");
|
|
397
|
-
const symlinkResult = createSymlink(centralPromptPath, symlinkPath, force);
|
|
398
|
-
if (symlinkResult.created)
|
|
399
|
-
result.symlinksCreated.push(`${symlinkPath} → ${centralPromptPath}`);
|
|
400
|
-
if (symlinkResult.skipped)
|
|
401
|
-
result.filesSkipped.push(symlinkPath);
|
|
402
|
-
} else {
|
|
403
|
-
const promptPath = join(homedir(), ".codex", "prompts", "hmy.md");
|
|
404
|
-
const { created, skipped } = writeFileIfNotExists(promptPath, promptContent, force);
|
|
405
|
-
if (created)
|
|
406
|
-
result.filesCreated.push(promptPath);
|
|
407
|
-
if (skipped)
|
|
408
|
-
result.filesSkipped.push(promptPath);
|
|
409
|
-
}
|
|
410
|
-
const agentsPath = join(cwd, "AGENTS.md");
|
|
411
|
-
const { created: agentsCreated, skipped: agentsSkipped } = writeFileIfNotExists(agentsPath, agentsContent, force);
|
|
412
|
-
if (agentsCreated)
|
|
413
|
-
result.filesCreated.push(agentsPath);
|
|
414
|
-
if (agentsSkipped)
|
|
415
|
-
result.filesSkipped.push(agentsPath);
|
|
416
|
-
const configPath = join(homedir(), ".codex", "config.toml");
|
|
417
|
-
const mcpConfigSection = `
|
|
418
|
-
# Harmony MCP Server
|
|
419
|
-
[mcp_servers.harmony]
|
|
420
|
-
command = "harmony-mcp"
|
|
421
|
-
args = ["serve"]
|
|
422
|
-
`;
|
|
423
|
-
if (!existsSync(configPath)) {
|
|
424
|
-
ensureDir(dirname(configPath));
|
|
425
|
-
writeFileSync(configPath, mcpConfigSection, "utf-8");
|
|
426
|
-
result.filesCreated.push(configPath);
|
|
427
|
-
} else {
|
|
428
|
-
const existingConfig = readFileSync(configPath, "utf-8");
|
|
429
|
-
if (!existingConfig.includes("[mcp_servers.harmony]")) {
|
|
430
|
-
writeFileSync(configPath, existingConfig + `
|
|
431
|
-
` + mcpConfigSection, "utf-8");
|
|
432
|
-
result.filesCreated.push(configPath);
|
|
433
|
-
} else if (force) {
|
|
434
|
-
const updated = existingConfig.replace(/\[mcp_servers\.harmony\][\s\S]*?(?=\[|$)/, mcpConfigSection.trim() + `
|
|
435
|
-
|
|
436
|
-
`);
|
|
437
|
-
writeFileSync(configPath, updated, "utf-8");
|
|
438
|
-
result.filesCreated.push(configPath);
|
|
439
|
-
} else {
|
|
440
|
-
result.filesSkipped.push(configPath);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
return result;
|
|
444
|
-
}
|
|
445
|
-
function initCursor(cwd, force, installMode) {
|
|
446
|
-
const result = {
|
|
447
|
-
agent: "cursor",
|
|
448
|
-
filesCreated: [],
|
|
449
|
-
filesSkipped: [],
|
|
450
|
-
symlinksCreated: []
|
|
451
|
-
};
|
|
452
|
-
const ruleContent = `---
|
|
453
|
-
description: Harmony card workflow rule
|
|
454
|
-
globs:
|
|
455
|
-
- "**/*"
|
|
456
|
-
alwaysApply: false
|
|
457
|
-
---
|
|
458
|
-
|
|
459
|
-
# Harmony Integration
|
|
460
|
-
|
|
461
|
-
When the user asks you to work on a Harmony card (references like #42, card names, or UUIDs):
|
|
462
|
-
|
|
463
|
-
${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Your agent identifier", "cursor").replace("Your agent name", "Cursor AI")}
|
|
464
|
-
`;
|
|
465
|
-
const mcpConfigPath = join(cwd, ".cursor", "mcp.json");
|
|
466
|
-
const mcpConfig = {
|
|
467
|
-
mcpServers: {
|
|
468
|
-
harmony: {
|
|
469
|
-
command: "harmony-mcp",
|
|
470
|
-
args: ["serve"]
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
};
|
|
474
|
-
const mcpResult = mergeJsonFile(mcpConfigPath, mcpConfig, force);
|
|
475
|
-
if (mcpResult.created || mcpResult.merged) {
|
|
476
|
-
result.filesCreated.push(mcpConfigPath);
|
|
477
|
-
} else if (mcpResult.skipped) {
|
|
478
|
-
result.filesSkipped.push(mcpConfigPath);
|
|
479
|
-
}
|
|
480
|
-
if (installMode === "global") {
|
|
481
|
-
const centralRulePath = join(GLOBAL_SKILLS_DIR, "cursor", "harmony.mdc");
|
|
482
|
-
const { created, skipped } = writeFileIfNotExists(centralRulePath, ruleContent, force);
|
|
483
|
-
if (created)
|
|
484
|
-
result.filesCreated.push(centralRulePath);
|
|
485
|
-
if (skipped)
|
|
486
|
-
result.filesSkipped.push(centralRulePath);
|
|
487
|
-
const symlinkPath = join(homedir(), ".cursor", "rules", "harmony.mdc");
|
|
488
|
-
const symlinkResult = createSymlink(centralRulePath, symlinkPath, force);
|
|
489
|
-
if (symlinkResult.created)
|
|
490
|
-
result.symlinksCreated.push(`${symlinkPath} → ${centralRulePath}`);
|
|
491
|
-
if (symlinkResult.skipped)
|
|
492
|
-
result.filesSkipped.push(symlinkPath);
|
|
493
|
-
} else {
|
|
494
|
-
const rulePath = join(cwd, ".cursor", "rules", "harmony.mdc");
|
|
495
|
-
const { created, skipped } = writeFileIfNotExists(rulePath, ruleContent, force);
|
|
496
|
-
if (created)
|
|
497
|
-
result.filesCreated.push(rulePath);
|
|
498
|
-
if (skipped)
|
|
499
|
-
result.filesSkipped.push(rulePath);
|
|
500
|
-
}
|
|
501
|
-
return result;
|
|
502
|
-
}
|
|
503
|
-
function initWindsurf(cwd, force, installMode) {
|
|
504
|
-
const result = {
|
|
505
|
-
agent: "windsurf",
|
|
506
|
-
filesCreated: [],
|
|
507
|
-
filesSkipped: [],
|
|
508
|
-
symlinksCreated: []
|
|
509
|
-
};
|
|
510
|
-
const ruleContent = `---
|
|
511
|
-
trigger: model_decision
|
|
512
|
-
description: Activate when user asks to work on a Harmony card (references like #42, card names, or task management)
|
|
513
|
-
---
|
|
514
|
-
|
|
515
|
-
# Harmony Card Workflow
|
|
516
|
-
|
|
517
|
-
When working on a Harmony card:
|
|
518
|
-
|
|
519
|
-
${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Your agent identifier", "windsurf").replace("Your agent name", "Windsurf AI")}
|
|
520
|
-
`;
|
|
521
|
-
const globalMcpPath = join(homedir(), ".codeium", "windsurf", "mcp_config.json");
|
|
522
|
-
const mcpConfig = {
|
|
523
|
-
mcpServers: {
|
|
524
|
-
harmony: {
|
|
525
|
-
command: "harmony-mcp",
|
|
526
|
-
args: ["serve"],
|
|
527
|
-
disabled: false,
|
|
528
|
-
alwaysAllow: []
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
};
|
|
532
|
-
const mcpResult = mergeJsonFile(globalMcpPath, mcpConfig, force);
|
|
533
|
-
if (mcpResult.created || mcpResult.merged) {
|
|
534
|
-
result.filesCreated.push(globalMcpPath);
|
|
535
|
-
} else if (mcpResult.skipped) {
|
|
536
|
-
result.filesSkipped.push(globalMcpPath);
|
|
537
|
-
}
|
|
538
|
-
if (installMode === "global") {
|
|
539
|
-
const centralRulePath = join(GLOBAL_SKILLS_DIR, "windsurf", "harmony.md");
|
|
540
|
-
const { created, skipped } = writeFileIfNotExists(centralRulePath, ruleContent, force);
|
|
541
|
-
if (created)
|
|
542
|
-
result.filesCreated.push(centralRulePath);
|
|
543
|
-
if (skipped)
|
|
544
|
-
result.filesSkipped.push(centralRulePath);
|
|
545
|
-
const symlinkPath = join(homedir(), ".codeium", "windsurf", "rules", "harmony.md");
|
|
546
|
-
const symlinkResult = createSymlink(centralRulePath, symlinkPath, force);
|
|
547
|
-
if (symlinkResult.created)
|
|
548
|
-
result.symlinksCreated.push(`${symlinkPath} → ${centralRulePath}`);
|
|
549
|
-
if (symlinkResult.skipped)
|
|
550
|
-
result.filesSkipped.push(symlinkPath);
|
|
551
|
-
} else {
|
|
552
|
-
const rulePath = join(cwd, ".windsurf", "rules", "harmony.md");
|
|
553
|
-
const { created, skipped } = writeFileIfNotExists(rulePath, ruleContent, force);
|
|
554
|
-
if (created)
|
|
555
|
-
result.filesCreated.push(rulePath);
|
|
556
|
-
if (skipped)
|
|
557
|
-
result.filesSkipped.push(rulePath);
|
|
558
|
-
}
|
|
559
|
-
return result;
|
|
560
|
-
}
|
|
561
|
-
function initHarmony(options = {}) {
|
|
562
|
-
const cwd = options.cwd || process.cwd();
|
|
563
|
-
const force = options.force || false;
|
|
564
|
-
const agents = options.agents || ["claude", "codex", "cursor", "windsurf"];
|
|
565
|
-
const installMode = options.installMode || "local";
|
|
566
|
-
const results = [];
|
|
567
|
-
for (const agent of agents) {
|
|
568
|
-
try {
|
|
569
|
-
switch (agent) {
|
|
570
|
-
case "claude":
|
|
571
|
-
results.push(initClaude(cwd, force, installMode));
|
|
572
|
-
break;
|
|
573
|
-
case "codex":
|
|
574
|
-
results.push(initCodex(cwd, force, installMode));
|
|
575
|
-
break;
|
|
576
|
-
case "cursor":
|
|
577
|
-
results.push(initCursor(cwd, force, installMode));
|
|
578
|
-
break;
|
|
579
|
-
case "windsurf":
|
|
580
|
-
results.push(initWindsurf(cwd, force, installMode));
|
|
581
|
-
break;
|
|
582
|
-
default:
|
|
583
|
-
results.push({
|
|
584
|
-
agent,
|
|
585
|
-
filesCreated: [],
|
|
586
|
-
filesSkipped: [],
|
|
587
|
-
symlinksCreated: [],
|
|
588
|
-
error: `Unknown agent: ${agent}`
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
} catch (error) {
|
|
592
|
-
results.push({
|
|
593
|
-
agent,
|
|
594
|
-
filesCreated: [],
|
|
595
|
-
filesSkipped: [],
|
|
596
|
-
symlinksCreated: [],
|
|
597
|
-
error: error instanceof Error ? error.message : String(error)
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
return results;
|
|
602
|
-
}
|
|
603
|
-
function detectAgents(cwd = process.cwd()) {
|
|
604
|
-
const detected = [];
|
|
605
|
-
if (existsSync(join(cwd, ".claude")) || existsSync(join(homedir(), ".claude"))) {
|
|
606
|
-
detected.push("claude");
|
|
607
|
-
}
|
|
608
|
-
if (existsSync(join(cwd, "AGENTS.md")) || existsSync(join(homedir(), ".codex"))) {
|
|
609
|
-
detected.push("codex");
|
|
610
|
-
}
|
|
611
|
-
if (existsSync(join(cwd, ".cursor"))) {
|
|
612
|
-
detected.push("cursor");
|
|
613
|
-
}
|
|
614
|
-
if (existsSync(join(cwd, ".windsurf")) || existsSync(join(cwd, ".windsurfrules")) || existsSync(join(homedir(), ".codeium", "windsurf"))) {
|
|
615
|
-
detected.push("windsurf");
|
|
616
|
-
}
|
|
617
|
-
return detected;
|
|
618
|
-
}
|
|
619
|
-
var SUPPORTED_AGENTS = ["claude", "codex", "cursor", "windsurf"];
|
|
620
|
-
export {
|
|
621
|
-
initHarmony,
|
|
622
|
-
detectAgents,
|
|
623
|
-
SUPPORTED_AGENTS
|
|
624
|
-
};
|