harmony-mcp 1.0.4 → 1.1.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 +48 -6
- package/dist/cli.js +643 -10
- package/dist/index.js +113 -0
- package/dist/init.js +413 -0
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# harmony-mcp
|
|
2
2
|
|
|
3
|
-
MCP (Model Context Protocol) server for Harmony Kanban board. Enables Claude Code,
|
|
3
|
+
MCP (Model Context Protocol) server for Harmony Kanban board. Enables AI coding agents (Claude Code, OpenAI Codex, Cursor, Windsurf) to interact with your Harmony boards.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **20+ MCP Tools** for full board control (cards, columns, labels, subtasks)
|
|
8
|
+
- **Agent Session Tracking** - track work progress with timer badges
|
|
9
|
+
- **Multi-Agent Support** - works with Claude Code, Codex, Cursor, Windsurf
|
|
10
|
+
- **One-Command Setup** - auto-configure all supported agents
|
|
8
11
|
- **Natural Language Processing** via voice-nlu edge function
|
|
9
12
|
- **API Key Authentication** - no database credentials required
|
|
10
13
|
|
|
@@ -29,19 +32,30 @@ npm install -g harmony-mcp
|
|
|
29
32
|
harmony-mcp configure --api-key hmy_your_key_here
|
|
30
33
|
```
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
### 4. Initialize for Your AI Agents
|
|
33
36
|
|
|
34
37
|
```bash
|
|
35
|
-
|
|
38
|
+
# Auto-detect and configure all installed agents
|
|
39
|
+
harmony-mcp init --detect
|
|
40
|
+
|
|
41
|
+
# Or configure all supported agents
|
|
42
|
+
harmony-mcp init --all
|
|
43
|
+
|
|
44
|
+
# Or configure specific agents
|
|
45
|
+
harmony-mcp init --agent claude codex cursor windsurf
|
|
36
46
|
```
|
|
37
47
|
|
|
38
|
-
|
|
48
|
+
This creates the necessary configuration files and workflow prompts for each agent.
|
|
49
|
+
|
|
50
|
+
### Manual Setup (Alternative)
|
|
51
|
+
|
|
52
|
+
If you prefer manual setup for Claude Code:
|
|
39
53
|
|
|
40
54
|
```bash
|
|
41
55
|
claude mcp add harmony -- harmony-mcp serve
|
|
42
56
|
```
|
|
43
57
|
|
|
44
|
-
Or add
|
|
58
|
+
Or add to `~/.claude/settings.json`:
|
|
45
59
|
|
|
46
60
|
```json
|
|
47
61
|
{
|
|
@@ -54,12 +68,34 @@ Or add manually to `~/.claude/settings.json`:
|
|
|
54
68
|
}
|
|
55
69
|
```
|
|
56
70
|
|
|
57
|
-
|
|
71
|
+
## Supported AI Agents
|
|
72
|
+
|
|
73
|
+
| Agent | MCP Config | Workflow Command |
|
|
74
|
+
|-------|-----------|------------------|
|
|
75
|
+
| **Claude Code** | `~/.claude/settings.json` | `/hmy #42` |
|
|
76
|
+
| **OpenAI Codex** | `~/.codex/config.toml` | `/prompts:hmy #42` |
|
|
77
|
+
| **Cursor** | `.cursor/mcp.json` | MCP tools auto-available |
|
|
78
|
+
| **Windsurf** | `~/.codeium/windsurf/mcp_config.json` | MCP tools auto-available |
|
|
79
|
+
|
|
80
|
+
## Card Workflow
|
|
81
|
+
|
|
82
|
+
When you start working on a card (e.g., `/hmy #42`):
|
|
83
|
+
|
|
84
|
+
1. **Find** - Locates the card by short ID, UUID, or name
|
|
85
|
+
2. **Move** - Moves the card to "In Progress" column
|
|
86
|
+
3. **Label** - Adds the "agent" label to indicate AI is working
|
|
87
|
+
4. **Track** - Starts a session timer visible in the UI
|
|
88
|
+
5. **Implement** - Work on the task with progress updates
|
|
89
|
+
6. **Complete** - Move to "Review" when done
|
|
58
90
|
|
|
59
91
|
## CLI Commands
|
|
60
92
|
|
|
61
93
|
```bash
|
|
62
94
|
harmony-mcp configure # Set up API key
|
|
95
|
+
harmony-mcp init # Initialize for AI agents (interactive)
|
|
96
|
+
harmony-mcp init --all # Configure all supported agents
|
|
97
|
+
harmony-mcp init --detect # Auto-detect and configure installed agents
|
|
98
|
+
harmony-mcp init --agent X Y # Configure specific agents
|
|
63
99
|
harmony-mcp status # Show current config
|
|
64
100
|
harmony-mcp reset # Clear configuration
|
|
65
101
|
harmony-mcp set-workspace ID # Set active workspace
|
|
@@ -105,6 +141,12 @@ harmony-mcp serve # Start MCP server
|
|
|
105
141
|
### Natural Language
|
|
106
142
|
- `harmony_process_command` - Process natural language commands
|
|
107
143
|
|
|
144
|
+
### Agent Session Tracking
|
|
145
|
+
- `harmony_start_agent_session` - Start tracking work on a card
|
|
146
|
+
- `harmony_update_agent_progress` - Update progress, status, blockers
|
|
147
|
+
- `harmony_end_agent_session` - End session (completed/paused)
|
|
148
|
+
- `harmony_get_agent_session` - Get current session state
|
|
149
|
+
|
|
108
150
|
## Direct API Access
|
|
109
151
|
|
|
110
152
|
You can also call the Harmony API directly from any HTTP client:
|
package/dist/cli.js
CHANGED
|
@@ -13786,6 +13786,386 @@ var require_dist = __commonJS((exports, module) => {
|
|
|
13786
13786
|
exports.default = formatsPlugin;
|
|
13787
13787
|
});
|
|
13788
13788
|
|
|
13789
|
+
// src/init.ts
|
|
13790
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
13791
|
+
import { join, dirname } from "node:path";
|
|
13792
|
+
import { homedir } from "node:os";
|
|
13793
|
+
var HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
|
|
13794
|
+
|
|
13795
|
+
You are starting work on a Harmony card. Follow this workflow:
|
|
13796
|
+
|
|
13797
|
+
## Step 1: Find the Card
|
|
13798
|
+
|
|
13799
|
+
The user provided: $ARGUMENTS
|
|
13800
|
+
|
|
13801
|
+
Parse the card reference:
|
|
13802
|
+
- If it starts with \`#\` followed by numbers (e.g., \`#42\`), use \`harmony_get_card_by_short_id\` with the number
|
|
13803
|
+
- If it looks like a UUID, use \`harmony_get_card\` directly
|
|
13804
|
+
- Otherwise, use \`harmony_search_cards\` to find by name
|
|
13805
|
+
|
|
13806
|
+
## Step 2: Move to In Progress
|
|
13807
|
+
|
|
13808
|
+
Once you have the card:
|
|
13809
|
+
1. Get the board using \`harmony_get_board\` to find the "In Progress" column ID
|
|
13810
|
+
2. Use \`harmony_move_card\` to move the card to "In Progress"
|
|
13811
|
+
|
|
13812
|
+
## Step 3: Add Agent Label
|
|
13813
|
+
|
|
13814
|
+
1. From the board response, find the label named "agent"
|
|
13815
|
+
2. Use \`harmony_add_label_to_card\` to add it to the card
|
|
13816
|
+
|
|
13817
|
+
## Step 4: Start Agent Session
|
|
13818
|
+
|
|
13819
|
+
After adding the agent label, start tracking your work session:
|
|
13820
|
+
1. Use \`harmony_start_agent_session\` with:
|
|
13821
|
+
- \`cardId\`: The card ID
|
|
13822
|
+
- \`agentIdentifier\`: Your agent identifier
|
|
13823
|
+
- \`agentName\`: Your agent name
|
|
13824
|
+
- \`currentTask\`: A brief description of what you're about to do
|
|
13825
|
+
|
|
13826
|
+
This enables the timer badge on the card to show your progress in real-time.
|
|
13827
|
+
|
|
13828
|
+
## Step 5: Display Card Details
|
|
13829
|
+
|
|
13830
|
+
Show the user:
|
|
13831
|
+
- Card title and short ID
|
|
13832
|
+
- Description (if any)
|
|
13833
|
+
- Priority
|
|
13834
|
+
- Current labels
|
|
13835
|
+
- Due date (if set)
|
|
13836
|
+
|
|
13837
|
+
## Step 6: Implement the Solution
|
|
13838
|
+
|
|
13839
|
+
Based on the card's title and description:
|
|
13840
|
+
1. Understand what needs to be done
|
|
13841
|
+
2. Explore the codebase as needed
|
|
13842
|
+
3. Implement the required changes
|
|
13843
|
+
4. Test the changes
|
|
13844
|
+
|
|
13845
|
+
**During implementation, update your progress periodically:**
|
|
13846
|
+
- Use \`harmony_update_agent_progress\` to report:
|
|
13847
|
+
- \`progressPercent\`: Estimated completion (0-100)
|
|
13848
|
+
- \`currentTask\`: What you're currently doing
|
|
13849
|
+
- \`blockers\`: Any issues blocking progress (set \`status: "blocked"\` if blocked)
|
|
13850
|
+
|
|
13851
|
+
## Step 7: Complete Work
|
|
13852
|
+
|
|
13853
|
+
When implementation is complete:
|
|
13854
|
+
1. Use \`harmony_end_agent_session\` with:
|
|
13855
|
+
- \`cardId\`: The card ID
|
|
13856
|
+
- \`status\`: "completed"
|
|
13857
|
+
- \`progressPercent\`: 100
|
|
13858
|
+
2. Use \`harmony_move_card\` to move the card to the "Review" column
|
|
13859
|
+
3. Summarize what was accomplished
|
|
13860
|
+
|
|
13861
|
+
## Important Notes
|
|
13862
|
+
- Always read the card description carefully before starting
|
|
13863
|
+
- If the task is unclear, ask for clarification
|
|
13864
|
+
- Make commits as appropriate during implementation
|
|
13865
|
+
- The "agent" label indicates AI is working on the card
|
|
13866
|
+
- Update progress at meaningful milestones, not constantly
|
|
13867
|
+
- If you need to pause work, call \`harmony_end_agent_session\` with \`status: "paused"\`
|
|
13868
|
+
`;
|
|
13869
|
+
function ensureDir(dirPath) {
|
|
13870
|
+
if (!existsSync(dirPath)) {
|
|
13871
|
+
mkdirSync(dirPath, { recursive: true });
|
|
13872
|
+
}
|
|
13873
|
+
}
|
|
13874
|
+
function writeFileIfNotExists(filePath, content, force) {
|
|
13875
|
+
if (existsSync(filePath) && !force) {
|
|
13876
|
+
return { created: false, skipped: true };
|
|
13877
|
+
}
|
|
13878
|
+
ensureDir(dirname(filePath));
|
|
13879
|
+
writeFileSync(filePath, content, "utf-8");
|
|
13880
|
+
return { created: true, skipped: false };
|
|
13881
|
+
}
|
|
13882
|
+
function mergeJsonFile(filePath, updates, force) {
|
|
13883
|
+
if (!existsSync(filePath)) {
|
|
13884
|
+
ensureDir(dirname(filePath));
|
|
13885
|
+
writeFileSync(filePath, JSON.stringify(updates, null, 2), "utf-8");
|
|
13886
|
+
return { created: true, skipped: false, merged: false };
|
|
13887
|
+
}
|
|
13888
|
+
try {
|
|
13889
|
+
const existing = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
13890
|
+
if (updates.mcpServers && existing.mcpServers) {
|
|
13891
|
+
if (existing.mcpServers.harmony && !force) {
|
|
13892
|
+
return { created: false, skipped: true, merged: false };
|
|
13893
|
+
}
|
|
13894
|
+
existing.mcpServers = { ...existing.mcpServers, ...updates.mcpServers };
|
|
13895
|
+
} else {
|
|
13896
|
+
Object.assign(existing, updates);
|
|
13897
|
+
}
|
|
13898
|
+
writeFileSync(filePath, JSON.stringify(existing, null, 2), "utf-8");
|
|
13899
|
+
return { created: false, skipped: false, merged: true };
|
|
13900
|
+
} catch {
|
|
13901
|
+
if (force) {
|
|
13902
|
+
writeFileSync(filePath, JSON.stringify(updates, null, 2), "utf-8");
|
|
13903
|
+
return { created: true, skipped: false, merged: false };
|
|
13904
|
+
}
|
|
13905
|
+
return { created: false, skipped: true, merged: false };
|
|
13906
|
+
}
|
|
13907
|
+
}
|
|
13908
|
+
function initClaude(cwd, force) {
|
|
13909
|
+
const result = {
|
|
13910
|
+
agent: "claude",
|
|
13911
|
+
filesCreated: [],
|
|
13912
|
+
filesSkipped: []
|
|
13913
|
+
};
|
|
13914
|
+
const commandContent = `---
|
|
13915
|
+
description: Start working on a Harmony card (moves to In Progress, adds agent label)
|
|
13916
|
+
argument-hint: <card-reference>
|
|
13917
|
+
---
|
|
13918
|
+
|
|
13919
|
+
${HARMONY_WORKFLOW_PROMPT.replace("Your agent identifier", "claude-code").replace("Your agent name", "Claude Code")}
|
|
13920
|
+
`;
|
|
13921
|
+
const commandPath = join(cwd, ".claude", "commands", "hmy.md");
|
|
13922
|
+
const { created, skipped } = writeFileIfNotExists(commandPath, commandContent, force);
|
|
13923
|
+
if (created)
|
|
13924
|
+
result.filesCreated.push(commandPath);
|
|
13925
|
+
if (skipped)
|
|
13926
|
+
result.filesSkipped.push(commandPath);
|
|
13927
|
+
const globalConfigPath = join(homedir(), ".claude", "settings.json");
|
|
13928
|
+
const mcpConfig = {
|
|
13929
|
+
mcpServers: {
|
|
13930
|
+
harmony: {
|
|
13931
|
+
command: "harmony-mcp",
|
|
13932
|
+
args: ["serve"]
|
|
13933
|
+
}
|
|
13934
|
+
}
|
|
13935
|
+
};
|
|
13936
|
+
const configResult = mergeJsonFile(globalConfigPath, mcpConfig, force);
|
|
13937
|
+
if (configResult.created || configResult.merged) {
|
|
13938
|
+
result.filesCreated.push(globalConfigPath);
|
|
13939
|
+
} else if (configResult.skipped) {
|
|
13940
|
+
result.filesSkipped.push(globalConfigPath);
|
|
13941
|
+
}
|
|
13942
|
+
return result;
|
|
13943
|
+
}
|
|
13944
|
+
function initCodex(cwd, force) {
|
|
13945
|
+
const result = {
|
|
13946
|
+
agent: "codex",
|
|
13947
|
+
filesCreated: [],
|
|
13948
|
+
filesSkipped: []
|
|
13949
|
+
};
|
|
13950
|
+
const agentsContent = `# Harmony Integration
|
|
13951
|
+
|
|
13952
|
+
This project uses Harmony for task management. When working on tasks:
|
|
13953
|
+
|
|
13954
|
+
## Starting Work on a Card
|
|
13955
|
+
|
|
13956
|
+
When given a card reference (e.g., #42 or a card name), follow this workflow:
|
|
13957
|
+
|
|
13958
|
+
1. Use \`harmony_get_card_by_short_id\` or \`harmony_search_cards\` to find the card
|
|
13959
|
+
2. Move the card to "In Progress" using \`harmony_move_card\`
|
|
13960
|
+
3. Add the "agent" label using \`harmony_add_label_to_card\`
|
|
13961
|
+
4. Start a session with \`harmony_start_agent_session\` (agentIdentifier: "codex", agentName: "OpenAI Codex")
|
|
13962
|
+
5. Show the card details to the user
|
|
13963
|
+
6. Implement the solution based on the card description
|
|
13964
|
+
7. Update progress periodically with \`harmony_update_agent_progress\`
|
|
13965
|
+
8. When done, call \`harmony_end_agent_session\` and move to "Review"
|
|
13966
|
+
|
|
13967
|
+
## Available Harmony Tools
|
|
13968
|
+
|
|
13969
|
+
- \`harmony_get_card\`, \`harmony_get_card_by_short_id\`, \`harmony_search_cards\` - Find cards
|
|
13970
|
+
- \`harmony_move_card\` - Move cards between columns
|
|
13971
|
+
- \`harmony_add_label_to_card\`, \`harmony_remove_label_from_card\` - Manage labels
|
|
13972
|
+
- \`harmony_start_agent_session\`, \`harmony_update_agent_progress\`, \`harmony_end_agent_session\` - Track work
|
|
13973
|
+
- \`harmony_get_board\` - Get board state
|
|
13974
|
+
`;
|
|
13975
|
+
const agentsPath = join(cwd, "AGENTS.md");
|
|
13976
|
+
const { created: agentsCreated, skipped: agentsSkipped } = writeFileIfNotExists(agentsPath, agentsContent, force);
|
|
13977
|
+
if (agentsCreated)
|
|
13978
|
+
result.filesCreated.push(agentsPath);
|
|
13979
|
+
if (agentsSkipped)
|
|
13980
|
+
result.filesSkipped.push(agentsPath);
|
|
13981
|
+
const promptContent = `---
|
|
13982
|
+
name: hmy
|
|
13983
|
+
description: Start working on a Harmony card
|
|
13984
|
+
arguments:
|
|
13985
|
+
- name: card
|
|
13986
|
+
description: Card reference (#42, UUID, or name)
|
|
13987
|
+
required: true
|
|
13988
|
+
---
|
|
13989
|
+
|
|
13990
|
+
${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "{{card}}").replace("Your agent identifier", "codex").replace("Your agent name", "OpenAI Codex")}
|
|
13991
|
+
`;
|
|
13992
|
+
const promptsDir = join(homedir(), ".codex", "prompts");
|
|
13993
|
+
const promptPath = join(promptsDir, "hmy.md");
|
|
13994
|
+
const { created: promptCreated, skipped: promptSkipped } = writeFileIfNotExists(promptPath, promptContent, force);
|
|
13995
|
+
if (promptCreated)
|
|
13996
|
+
result.filesCreated.push(promptPath);
|
|
13997
|
+
if (promptSkipped)
|
|
13998
|
+
result.filesSkipped.push(promptPath);
|
|
13999
|
+
const configPath = join(homedir(), ".codex", "config.toml");
|
|
14000
|
+
const mcpConfigSection = `
|
|
14001
|
+
# Harmony MCP Server
|
|
14002
|
+
[mcp_servers.harmony]
|
|
14003
|
+
command = "harmony-mcp"
|
|
14004
|
+
args = ["serve"]
|
|
14005
|
+
`;
|
|
14006
|
+
if (!existsSync(configPath)) {
|
|
14007
|
+
ensureDir(dirname(configPath));
|
|
14008
|
+
writeFileSync(configPath, mcpConfigSection, "utf-8");
|
|
14009
|
+
result.filesCreated.push(configPath);
|
|
14010
|
+
} else {
|
|
14011
|
+
const existingConfig = readFileSync(configPath, "utf-8");
|
|
14012
|
+
if (!existingConfig.includes("[mcp_servers.harmony]")) {
|
|
14013
|
+
writeFileSync(configPath, existingConfig + `
|
|
14014
|
+
` + mcpConfigSection, "utf-8");
|
|
14015
|
+
result.filesCreated.push(configPath);
|
|
14016
|
+
} else if (force) {
|
|
14017
|
+
const updated = existingConfig.replace(/\[mcp_servers\.harmony\][\s\S]*?(?=\[|$)/, mcpConfigSection.trim() + `
|
|
14018
|
+
|
|
14019
|
+
`);
|
|
14020
|
+
writeFileSync(configPath, updated, "utf-8");
|
|
14021
|
+
result.filesCreated.push(configPath);
|
|
14022
|
+
} else {
|
|
14023
|
+
result.filesSkipped.push(configPath);
|
|
14024
|
+
}
|
|
14025
|
+
}
|
|
14026
|
+
return result;
|
|
14027
|
+
}
|
|
14028
|
+
function initCursor(cwd, force) {
|
|
14029
|
+
const result = {
|
|
14030
|
+
agent: "cursor",
|
|
14031
|
+
filesCreated: [],
|
|
14032
|
+
filesSkipped: []
|
|
14033
|
+
};
|
|
14034
|
+
const mcpConfigPath = join(cwd, ".cursor", "mcp.json");
|
|
14035
|
+
const mcpConfig = {
|
|
14036
|
+
mcpServers: {
|
|
14037
|
+
harmony: {
|
|
14038
|
+
command: "harmony-mcp",
|
|
14039
|
+
args: ["serve"]
|
|
14040
|
+
}
|
|
14041
|
+
}
|
|
14042
|
+
};
|
|
14043
|
+
const mcpResult = mergeJsonFile(mcpConfigPath, mcpConfig, force);
|
|
14044
|
+
if (mcpResult.created || mcpResult.merged) {
|
|
14045
|
+
result.filesCreated.push(mcpConfigPath);
|
|
14046
|
+
} else if (mcpResult.skipped) {
|
|
14047
|
+
result.filesSkipped.push(mcpConfigPath);
|
|
14048
|
+
}
|
|
14049
|
+
const ruleContent = `---
|
|
14050
|
+
description: Harmony card workflow rule
|
|
14051
|
+
globs:
|
|
14052
|
+
- "**/*"
|
|
14053
|
+
alwaysApply: false
|
|
14054
|
+
---
|
|
14055
|
+
|
|
14056
|
+
# Harmony Integration
|
|
14057
|
+
|
|
14058
|
+
When the user asks you to work on a Harmony card (references like #42, card names, or UUIDs):
|
|
14059
|
+
|
|
14060
|
+
${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Your agent identifier", "cursor").replace("Your agent name", "Cursor AI")}
|
|
14061
|
+
`;
|
|
14062
|
+
const rulePath = join(cwd, ".cursor", "rules", "harmony.mdc");
|
|
14063
|
+
const { created: ruleCreated, skipped: ruleSkipped } = writeFileIfNotExists(rulePath, ruleContent, force);
|
|
14064
|
+
if (ruleCreated)
|
|
14065
|
+
result.filesCreated.push(rulePath);
|
|
14066
|
+
if (ruleSkipped)
|
|
14067
|
+
result.filesSkipped.push(rulePath);
|
|
14068
|
+
return result;
|
|
14069
|
+
}
|
|
14070
|
+
function initWindsurf(cwd, force) {
|
|
14071
|
+
const result = {
|
|
14072
|
+
agent: "windsurf",
|
|
14073
|
+
filesCreated: [],
|
|
14074
|
+
filesSkipped: []
|
|
14075
|
+
};
|
|
14076
|
+
const globalMcpPath = join(homedir(), ".codeium", "windsurf", "mcp_config.json");
|
|
14077
|
+
const mcpConfig = {
|
|
14078
|
+
mcpServers: {
|
|
14079
|
+
harmony: {
|
|
14080
|
+
command: "harmony-mcp",
|
|
14081
|
+
args: ["serve"],
|
|
14082
|
+
disabled: false,
|
|
14083
|
+
alwaysAllow: []
|
|
14084
|
+
}
|
|
14085
|
+
}
|
|
14086
|
+
};
|
|
14087
|
+
const mcpResult = mergeJsonFile(globalMcpPath, mcpConfig, force);
|
|
14088
|
+
if (mcpResult.created || mcpResult.merged) {
|
|
14089
|
+
result.filesCreated.push(globalMcpPath);
|
|
14090
|
+
} else if (mcpResult.skipped) {
|
|
14091
|
+
result.filesSkipped.push(globalMcpPath);
|
|
14092
|
+
}
|
|
14093
|
+
const ruleContent = `---
|
|
14094
|
+
trigger: model_decision
|
|
14095
|
+
description: Activate when user asks to work on a Harmony card (references like #42, card names, or task management)
|
|
14096
|
+
---
|
|
14097
|
+
|
|
14098
|
+
# Harmony Card Workflow
|
|
14099
|
+
|
|
14100
|
+
When working on a Harmony card:
|
|
14101
|
+
|
|
14102
|
+
${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Your agent identifier", "windsurf").replace("Your agent name", "Windsurf AI")}
|
|
14103
|
+
`;
|
|
14104
|
+
const rulePath = join(cwd, ".windsurf", "rules", "harmony.md");
|
|
14105
|
+
const { created: ruleCreated, skipped: ruleSkipped } = writeFileIfNotExists(rulePath, ruleContent, force);
|
|
14106
|
+
if (ruleCreated)
|
|
14107
|
+
result.filesCreated.push(rulePath);
|
|
14108
|
+
if (ruleSkipped)
|
|
14109
|
+
result.filesSkipped.push(rulePath);
|
|
14110
|
+
return result;
|
|
14111
|
+
}
|
|
14112
|
+
function initHarmony(options = {}) {
|
|
14113
|
+
const cwd = options.cwd || process.cwd();
|
|
14114
|
+
const force = options.force || false;
|
|
14115
|
+
const agents = options.agents || ["claude", "codex", "cursor", "windsurf"];
|
|
14116
|
+
const results = [];
|
|
14117
|
+
for (const agent of agents) {
|
|
14118
|
+
try {
|
|
14119
|
+
switch (agent) {
|
|
14120
|
+
case "claude":
|
|
14121
|
+
results.push(initClaude(cwd, force));
|
|
14122
|
+
break;
|
|
14123
|
+
case "codex":
|
|
14124
|
+
results.push(initCodex(cwd, force));
|
|
14125
|
+
break;
|
|
14126
|
+
case "cursor":
|
|
14127
|
+
results.push(initCursor(cwd, force));
|
|
14128
|
+
break;
|
|
14129
|
+
case "windsurf":
|
|
14130
|
+
results.push(initWindsurf(cwd, force));
|
|
14131
|
+
break;
|
|
14132
|
+
default:
|
|
14133
|
+
results.push({
|
|
14134
|
+
agent,
|
|
14135
|
+
filesCreated: [],
|
|
14136
|
+
filesSkipped: [],
|
|
14137
|
+
error: `Unknown agent: ${agent}`
|
|
14138
|
+
});
|
|
14139
|
+
}
|
|
14140
|
+
} catch (error) {
|
|
14141
|
+
results.push({
|
|
14142
|
+
agent,
|
|
14143
|
+
filesCreated: [],
|
|
14144
|
+
filesSkipped: [],
|
|
14145
|
+
error: error instanceof Error ? error.message : String(error)
|
|
14146
|
+
});
|
|
14147
|
+
}
|
|
14148
|
+
}
|
|
14149
|
+
return results;
|
|
14150
|
+
}
|
|
14151
|
+
function detectAgents(cwd = process.cwd()) {
|
|
14152
|
+
const detected = [];
|
|
14153
|
+
if (existsSync(join(cwd, ".claude")) || existsSync(join(homedir(), ".claude"))) {
|
|
14154
|
+
detected.push("claude");
|
|
14155
|
+
}
|
|
14156
|
+
if (existsSync(join(cwd, "AGENTS.md")) || existsSync(join(homedir(), ".codex"))) {
|
|
14157
|
+
detected.push("codex");
|
|
14158
|
+
}
|
|
14159
|
+
if (existsSync(join(cwd, ".cursor"))) {
|
|
14160
|
+
detected.push("cursor");
|
|
14161
|
+
}
|
|
14162
|
+
if (existsSync(join(cwd, ".windsurf")) || existsSync(join(cwd, ".windsurfrules")) || existsSync(join(homedir(), ".codeium", "windsurf"))) {
|
|
14163
|
+
detected.push("windsurf");
|
|
14164
|
+
}
|
|
14165
|
+
return detected;
|
|
14166
|
+
}
|
|
14167
|
+
var SUPPORTED_AGENTS = ["claude", "codex", "cursor", "windsurf"];
|
|
14168
|
+
|
|
13789
14169
|
// ../../node_modules/commander/esm.mjs
|
|
13790
14170
|
var import__ = __toESM(require_commander(), 1);
|
|
13791
14171
|
var {
|
|
@@ -24850,19 +25230,19 @@ var coerce = {
|
|
|
24850
25230
|
};
|
|
24851
25231
|
var NEVER2 = INVALID;
|
|
24852
25232
|
// src/config.ts
|
|
24853
|
-
import { homedir } from "node:os";
|
|
24854
|
-
import { join } from "node:path";
|
|
24855
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
25233
|
+
import { homedir as homedir2 } from "node:os";
|
|
25234
|
+
import { join as join2 } from "node:path";
|
|
25235
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
24856
25236
|
var DEFAULT_API_URL = "https://gethmy.com/api";
|
|
24857
25237
|
function getConfigDir() {
|
|
24858
|
-
return
|
|
25238
|
+
return join2(homedir2(), ".harmony-mcp");
|
|
24859
25239
|
}
|
|
24860
25240
|
function getConfigPath() {
|
|
24861
|
-
return
|
|
25241
|
+
return join2(getConfigDir(), "config.json");
|
|
24862
25242
|
}
|
|
24863
25243
|
function loadConfig() {
|
|
24864
25244
|
const configPath = getConfigPath();
|
|
24865
|
-
if (!
|
|
25245
|
+
if (!existsSync2(configPath)) {
|
|
24866
25246
|
return {
|
|
24867
25247
|
apiKey: null,
|
|
24868
25248
|
apiUrl: DEFAULT_API_URL,
|
|
@@ -24871,7 +25251,7 @@ function loadConfig() {
|
|
|
24871
25251
|
};
|
|
24872
25252
|
}
|
|
24873
25253
|
try {
|
|
24874
|
-
const data =
|
|
25254
|
+
const data = readFileSync2(configPath, "utf-8");
|
|
24875
25255
|
const config2 = JSON.parse(data);
|
|
24876
25256
|
return {
|
|
24877
25257
|
apiKey: config2.apiKey || null,
|
|
@@ -24891,12 +25271,12 @@ function loadConfig() {
|
|
|
24891
25271
|
function saveConfig(config2) {
|
|
24892
25272
|
const configDir = getConfigDir();
|
|
24893
25273
|
const configPath = getConfigPath();
|
|
24894
|
-
if (!
|
|
24895
|
-
|
|
25274
|
+
if (!existsSync2(configDir)) {
|
|
25275
|
+
mkdirSync2(configDir, { recursive: true, mode: 448 });
|
|
24896
25276
|
}
|
|
24897
25277
|
const existingConfig = loadConfig();
|
|
24898
25278
|
const newConfig = { ...existingConfig, ...config2 };
|
|
24899
|
-
|
|
25279
|
+
writeFileSync2(configPath, JSON.stringify(newConfig, null, 2), { mode: 384 });
|
|
24900
25280
|
}
|
|
24901
25281
|
function getApiKey() {
|
|
24902
25282
|
const config2 = loadConfig();
|
|
@@ -25025,6 +25405,22 @@ class HarmonyApiClient {
|
|
|
25025
25405
|
async deleteSubtask(subtaskId) {
|
|
25026
25406
|
return this.request("DELETE", `/subtasks/${subtaskId}`);
|
|
25027
25407
|
}
|
|
25408
|
+
async startAgentSession(cardId, data) {
|
|
25409
|
+
return this.request("POST", `/cards/${cardId}/agent-context`, data);
|
|
25410
|
+
}
|
|
25411
|
+
async updateAgentProgress(cardId, data) {
|
|
25412
|
+
return this.request("POST", `/cards/${cardId}/agent-context`, data);
|
|
25413
|
+
}
|
|
25414
|
+
async endAgentSession(cardId, data) {
|
|
25415
|
+
return this.request("DELETE", `/cards/${cardId}/agent-context`, data);
|
|
25416
|
+
}
|
|
25417
|
+
async getAgentSession(cardId, options) {
|
|
25418
|
+
const params = new URLSearchParams;
|
|
25419
|
+
if (options?.includeEnded)
|
|
25420
|
+
params.set("include_ended", "true");
|
|
25421
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
25422
|
+
return this.request("GET", `/cards/${cardId}/agent-context${query}`);
|
|
25423
|
+
}
|
|
25028
25424
|
async processNLU(data) {
|
|
25029
25425
|
return this.request("POST", "/nlu", data);
|
|
25030
25426
|
}
|
|
@@ -25309,6 +25705,60 @@ var TOOLS = {
|
|
|
25309
25705
|
},
|
|
25310
25706
|
required: ["command"]
|
|
25311
25707
|
}
|
|
25708
|
+
},
|
|
25709
|
+
harmony_start_agent_session: {
|
|
25710
|
+
description: "Start an agent work session on a card. Tracks progress, status, and blockers. Call this when beginning work on a card.",
|
|
25711
|
+
inputSchema: {
|
|
25712
|
+
type: "object",
|
|
25713
|
+
properties: {
|
|
25714
|
+
cardId: { type: "string", description: "Card ID to start working on" },
|
|
25715
|
+
agentIdentifier: { type: "string", description: "Unique agent identifier (e.g., claude-code)" },
|
|
25716
|
+
agentName: { type: "string", description: "Human-readable agent name (e.g., Claude Code)" },
|
|
25717
|
+
currentTask: { type: "string", description: "Initial task description" },
|
|
25718
|
+
estimatedMinutesRemaining: { type: "number", description: "Estimated time to completion in minutes" }
|
|
25719
|
+
},
|
|
25720
|
+
required: ["cardId", "agentIdentifier", "agentName"]
|
|
25721
|
+
}
|
|
25722
|
+
},
|
|
25723
|
+
harmony_update_agent_progress: {
|
|
25724
|
+
description: "Update progress on an active agent session. Use to report progress percentage, current task, blockers, or status changes.",
|
|
25725
|
+
inputSchema: {
|
|
25726
|
+
type: "object",
|
|
25727
|
+
properties: {
|
|
25728
|
+
cardId: { type: "string", description: "Card ID with active session" },
|
|
25729
|
+
agentIdentifier: { type: "string", description: "Agent identifier" },
|
|
25730
|
+
agentName: { type: "string", description: "Agent name" },
|
|
25731
|
+
status: { type: "string", enum: ["working", "blocked", "paused"], description: "Current status" },
|
|
25732
|
+
progressPercent: { type: "number", description: "Progress percentage (0-100)" },
|
|
25733
|
+
currentTask: { type: "string", description: "What the agent is currently doing" },
|
|
25734
|
+
blockers: { type: "array", items: { type: "string" }, description: "List of blocking issues" },
|
|
25735
|
+
estimatedMinutesRemaining: { type: "number", description: "Updated time estimate" }
|
|
25736
|
+
},
|
|
25737
|
+
required: ["cardId", "agentIdentifier", "agentName"]
|
|
25738
|
+
}
|
|
25739
|
+
},
|
|
25740
|
+
harmony_end_agent_session: {
|
|
25741
|
+
description: "End an agent work session on a card. Call this when work is complete or paused.",
|
|
25742
|
+
inputSchema: {
|
|
25743
|
+
type: "object",
|
|
25744
|
+
properties: {
|
|
25745
|
+
cardId: { type: "string", description: "Card ID to end session on" },
|
|
25746
|
+
status: { type: "string", enum: ["completed", "paused"], description: "Final status (default: completed)" },
|
|
25747
|
+
progressPercent: { type: "number", description: "Final progress percentage" }
|
|
25748
|
+
},
|
|
25749
|
+
required: ["cardId"]
|
|
25750
|
+
}
|
|
25751
|
+
},
|
|
25752
|
+
harmony_get_agent_session: {
|
|
25753
|
+
description: "Get the current agent session for a card, including progress, status, and blockers.",
|
|
25754
|
+
inputSchema: {
|
|
25755
|
+
type: "object",
|
|
25756
|
+
properties: {
|
|
25757
|
+
cardId: { type: "string", description: "Card ID to check" },
|
|
25758
|
+
includeEnded: { type: "boolean", description: "Include ended sessions in history" }
|
|
25759
|
+
},
|
|
25760
|
+
required: ["cardId"]
|
|
25761
|
+
}
|
|
25312
25762
|
}
|
|
25313
25763
|
};
|
|
25314
25764
|
var RESOURCES = [
|
|
@@ -25625,6 +26075,49 @@ Include: cards moved recently, current in-progress items, blocked or high-priori
|
|
|
25625
26075
|
});
|
|
25626
26076
|
return { success: true, ...result };
|
|
25627
26077
|
}
|
|
26078
|
+
case "harmony_start_agent_session": {
|
|
26079
|
+
const cardId = exports_external.string().uuid().parse(args.cardId);
|
|
26080
|
+
const agentIdentifier = exports_external.string().min(1).parse(args.agentIdentifier);
|
|
26081
|
+
const agentName = exports_external.string().min(1).parse(args.agentName);
|
|
26082
|
+
const result = await client2.startAgentSession(cardId, {
|
|
26083
|
+
agentIdentifier,
|
|
26084
|
+
agentName,
|
|
26085
|
+
status: "working",
|
|
26086
|
+
currentTask: args.currentTask,
|
|
26087
|
+
estimatedMinutesRemaining: args.estimatedMinutesRemaining
|
|
26088
|
+
});
|
|
26089
|
+
return { success: true, ...result };
|
|
26090
|
+
}
|
|
26091
|
+
case "harmony_update_agent_progress": {
|
|
26092
|
+
const cardId = exports_external.string().uuid().parse(args.cardId);
|
|
26093
|
+
const agentIdentifier = exports_external.string().min(1).parse(args.agentIdentifier);
|
|
26094
|
+
const agentName = exports_external.string().min(1).parse(args.agentName);
|
|
26095
|
+
const result = await client2.updateAgentProgress(cardId, {
|
|
26096
|
+
agentIdentifier,
|
|
26097
|
+
agentName,
|
|
26098
|
+
status: args.status,
|
|
26099
|
+
progressPercent: args.progressPercent,
|
|
26100
|
+
currentTask: args.currentTask,
|
|
26101
|
+
blockers: args.blockers,
|
|
26102
|
+
estimatedMinutesRemaining: args.estimatedMinutesRemaining
|
|
26103
|
+
});
|
|
26104
|
+
return { success: true, ...result };
|
|
26105
|
+
}
|
|
26106
|
+
case "harmony_end_agent_session": {
|
|
26107
|
+
const cardId = exports_external.string().uuid().parse(args.cardId);
|
|
26108
|
+
const result = await client2.endAgentSession(cardId, {
|
|
26109
|
+
status: args.status || "completed",
|
|
26110
|
+
progressPercent: args.progressPercent
|
|
26111
|
+
});
|
|
26112
|
+
return { success: true, ...result };
|
|
26113
|
+
}
|
|
26114
|
+
case "harmony_get_agent_session": {
|
|
26115
|
+
const cardId = exports_external.string().uuid().parse(args.cardId);
|
|
26116
|
+
const result = await client2.getAgentSession(cardId, {
|
|
26117
|
+
includeEnded: args.includeEnded === true || args.includeEnded === "true"
|
|
26118
|
+
});
|
|
26119
|
+
return { success: true, ...result };
|
|
26120
|
+
}
|
|
25628
26121
|
default:
|
|
25629
26122
|
throw new Error(`Unknown tool: ${name}`);
|
|
25630
26123
|
}
|
|
@@ -25733,4 +26226,144 @@ program.command("set-project <projectId>").description("Set the active project c
|
|
|
25733
26226
|
setActiveProject(projectId);
|
|
25734
26227
|
console.log(`Active project set to: ${projectId}`);
|
|
25735
26228
|
});
|
|
26229
|
+
program.command("init").description("Initialize Harmony MCP for AI coding agents (Claude Code, Codex, Cursor, Windsurf)").option("-a, --agent <agents...>", `Agent(s) to configure: ${SUPPORTED_AGENTS.join(", ")}`).option("--all", "Configure all supported agents").option("--detect", "Auto-detect installed agents and configure them").option("-f, --force", "Overwrite existing configuration files").option("-d, --directory <path>", "Project directory (default: current directory)").action(async (options) => {
|
|
26230
|
+
console.log(`Harmony MCP Initialization
|
|
26231
|
+
`);
|
|
26232
|
+
let agents = [];
|
|
26233
|
+
const cwd = options.directory || process.cwd();
|
|
26234
|
+
if (options.all) {
|
|
26235
|
+
agents = [...SUPPORTED_AGENTS];
|
|
26236
|
+
console.log(`Configuring all supported agents...
|
|
26237
|
+
`);
|
|
26238
|
+
} else if (options.detect) {
|
|
26239
|
+
agents = detectAgents(cwd);
|
|
26240
|
+
if (agents.length === 0) {
|
|
26241
|
+
console.log("No AI coding agents detected in this project.");
|
|
26242
|
+
console.log(`Supported agents: ${SUPPORTED_AGENTS.join(", ")}`);
|
|
26243
|
+
console.log(`
|
|
26244
|
+
Use --all to configure all agents, or --agent to specify specific ones.`);
|
|
26245
|
+
process.exit(0);
|
|
26246
|
+
}
|
|
26247
|
+
console.log(`Detected agents: ${agents.join(", ")}
|
|
26248
|
+
`);
|
|
26249
|
+
} else if (options.agent) {
|
|
26250
|
+
agents = options.agent.filter((a) => SUPPORTED_AGENTS.includes(a));
|
|
26251
|
+
const invalid = options.agent.filter((a) => !SUPPORTED_AGENTS.includes(a));
|
|
26252
|
+
if (invalid.length > 0) {
|
|
26253
|
+
console.log(`Warning: Unknown agent(s) ignored: ${invalid.join(", ")}`);
|
|
26254
|
+
}
|
|
26255
|
+
if (agents.length === 0) {
|
|
26256
|
+
console.log(`No valid agents specified. Supported: ${SUPPORTED_AGENTS.join(", ")}`);
|
|
26257
|
+
process.exit(1);
|
|
26258
|
+
}
|
|
26259
|
+
} else {
|
|
26260
|
+
console.log(`Select AI coding agents to configure:
|
|
26261
|
+
`);
|
|
26262
|
+
SUPPORTED_AGENTS.forEach((agent, i) => {
|
|
26263
|
+
const descriptions = {
|
|
26264
|
+
claude: "Claude Code - Anthropic CLI agent",
|
|
26265
|
+
codex: "OpenAI Codex - OpenAI coding agent",
|
|
26266
|
+
cursor: "Cursor - AI-powered IDE",
|
|
26267
|
+
windsurf: "Windsurf - Codeium AI IDE"
|
|
26268
|
+
};
|
|
26269
|
+
console.log(` ${i + 1}. ${agent} - ${descriptions[agent]}`);
|
|
26270
|
+
});
|
|
26271
|
+
console.log(` a. All agents`);
|
|
26272
|
+
console.log(` d. Auto-detect
|
|
26273
|
+
`);
|
|
26274
|
+
const answer = await prompt("Enter your choice (1-4, a, d, or comma-separated numbers): ");
|
|
26275
|
+
if (answer.toLowerCase() === "a") {
|
|
26276
|
+
agents = [...SUPPORTED_AGENTS];
|
|
26277
|
+
} else if (answer.toLowerCase() === "d") {
|
|
26278
|
+
agents = detectAgents(cwd);
|
|
26279
|
+
if (agents.length === 0) {
|
|
26280
|
+
console.log(`
|
|
26281
|
+
No agents detected. Using all agents.`);
|
|
26282
|
+
agents = [...SUPPORTED_AGENTS];
|
|
26283
|
+
} else {
|
|
26284
|
+
console.log(`
|
|
26285
|
+
Detected: ${agents.join(", ")}`);
|
|
26286
|
+
}
|
|
26287
|
+
} else {
|
|
26288
|
+
const selections = answer.split(/[,\s]+/).map((s) => parseInt(s.trim(), 10) - 1);
|
|
26289
|
+
agents = selections.filter((i) => i >= 0 && i < SUPPORTED_AGENTS.length).map((i) => SUPPORTED_AGENTS[i]);
|
|
26290
|
+
if (agents.length === 0) {
|
|
26291
|
+
console.log("No valid selections. Exiting.");
|
|
26292
|
+
rl.close();
|
|
26293
|
+
process.exit(1);
|
|
26294
|
+
}
|
|
26295
|
+
}
|
|
26296
|
+
rl.close();
|
|
26297
|
+
}
|
|
26298
|
+
console.log(`
|
|
26299
|
+
Initializing for: ${agents.join(", ")}
|
|
26300
|
+
`);
|
|
26301
|
+
const results = initHarmony({
|
|
26302
|
+
agents,
|
|
26303
|
+
cwd,
|
|
26304
|
+
force: options.force
|
|
26305
|
+
});
|
|
26306
|
+
let hasErrors = false;
|
|
26307
|
+
for (const result of results) {
|
|
26308
|
+
console.log(`
|
|
26309
|
+
${result.agent.toUpperCase()}:`);
|
|
26310
|
+
if (result.error) {
|
|
26311
|
+
console.log(` ❌ Error: ${result.error}`);
|
|
26312
|
+
hasErrors = true;
|
|
26313
|
+
continue;
|
|
26314
|
+
}
|
|
26315
|
+
if (result.filesCreated.length > 0) {
|
|
26316
|
+
console.log(" Created:");
|
|
26317
|
+
result.filesCreated.forEach((f) => console.log(` ✓ ${f}`));
|
|
26318
|
+
}
|
|
26319
|
+
if (result.filesSkipped.length > 0) {
|
|
26320
|
+
console.log(" Skipped (already exists, use --force to overwrite):");
|
|
26321
|
+
result.filesSkipped.forEach((f) => console.log(` - ${f}`));
|
|
26322
|
+
}
|
|
26323
|
+
if (result.filesCreated.length === 0 && result.filesSkipped.length === 0) {
|
|
26324
|
+
console.log(" No changes made");
|
|
26325
|
+
}
|
|
26326
|
+
}
|
|
26327
|
+
console.log(`
|
|
26328
|
+
` + "─".repeat(60));
|
|
26329
|
+
if (!isConfigured()) {
|
|
26330
|
+
console.log(`
|
|
26331
|
+
⚠️ API key not configured!`);
|
|
26332
|
+
console.log("Run: harmony-mcp configure");
|
|
26333
|
+
console.log(`Generate an API key at: https://gethmy.com → Settings → API Keys
|
|
26334
|
+
`);
|
|
26335
|
+
} else {
|
|
26336
|
+
console.log(`
|
|
26337
|
+
✓ Harmony MCP is ready to use!`);
|
|
26338
|
+
}
|
|
26339
|
+
console.log(`
|
|
26340
|
+
Usage by agent:`);
|
|
26341
|
+
if (agents.includes("claude")) {
|
|
26342
|
+
console.log(`
|
|
26343
|
+
Claude Code:`);
|
|
26344
|
+
console.log(" Use /hmy <card-reference> to start working on a card");
|
|
26345
|
+
console.log(' Example: /hmy #42 or /hmy "Fix login bug"');
|
|
26346
|
+
}
|
|
26347
|
+
if (agents.includes("codex")) {
|
|
26348
|
+
console.log(`
|
|
26349
|
+
Codex:`);
|
|
26350
|
+
console.log(" Use /prompts:hmy <card-reference> to start working on a card");
|
|
26351
|
+
console.log(" Or reference cards naturally - AGENTS.md provides context");
|
|
26352
|
+
}
|
|
26353
|
+
if (agents.includes("cursor")) {
|
|
26354
|
+
console.log(`
|
|
26355
|
+
Cursor:`);
|
|
26356
|
+
console.log(" MCP tools are available automatically");
|
|
26357
|
+
console.log(" Rules activate when you mention Harmony cards");
|
|
26358
|
+
}
|
|
26359
|
+
if (agents.includes("windsurf")) {
|
|
26360
|
+
console.log(`
|
|
26361
|
+
Windsurf:`);
|
|
26362
|
+
console.log(" MCP tools are available automatically");
|
|
26363
|
+
console.log(" Rules activate when you mention Harmony cards");
|
|
26364
|
+
}
|
|
26365
|
+
console.log(`
|
|
26366
|
+
`);
|
|
26367
|
+
process.exit(hasErrors ? 1 : 0);
|
|
26368
|
+
});
|
|
25736
26369
|
program.parse();
|
package/dist/index.js
CHANGED
|
@@ -23167,6 +23167,22 @@ class HarmonyApiClient {
|
|
|
23167
23167
|
async deleteSubtask(subtaskId) {
|
|
23168
23168
|
return this.request("DELETE", `/subtasks/${subtaskId}`);
|
|
23169
23169
|
}
|
|
23170
|
+
async startAgentSession(cardId, data) {
|
|
23171
|
+
return this.request("POST", `/cards/${cardId}/agent-context`, data);
|
|
23172
|
+
}
|
|
23173
|
+
async updateAgentProgress(cardId, data) {
|
|
23174
|
+
return this.request("POST", `/cards/${cardId}/agent-context`, data);
|
|
23175
|
+
}
|
|
23176
|
+
async endAgentSession(cardId, data) {
|
|
23177
|
+
return this.request("DELETE", `/cards/${cardId}/agent-context`, data);
|
|
23178
|
+
}
|
|
23179
|
+
async getAgentSession(cardId, options) {
|
|
23180
|
+
const params = new URLSearchParams;
|
|
23181
|
+
if (options?.includeEnded)
|
|
23182
|
+
params.set("include_ended", "true");
|
|
23183
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
23184
|
+
return this.request("GET", `/cards/${cardId}/agent-context${query}`);
|
|
23185
|
+
}
|
|
23170
23186
|
async processNLU(data) {
|
|
23171
23187
|
return this.request("POST", "/nlu", data);
|
|
23172
23188
|
}
|
|
@@ -23451,6 +23467,60 @@ var TOOLS = {
|
|
|
23451
23467
|
},
|
|
23452
23468
|
required: ["command"]
|
|
23453
23469
|
}
|
|
23470
|
+
},
|
|
23471
|
+
harmony_start_agent_session: {
|
|
23472
|
+
description: "Start an agent work session on a card. Tracks progress, status, and blockers. Call this when beginning work on a card.",
|
|
23473
|
+
inputSchema: {
|
|
23474
|
+
type: "object",
|
|
23475
|
+
properties: {
|
|
23476
|
+
cardId: { type: "string", description: "Card ID to start working on" },
|
|
23477
|
+
agentIdentifier: { type: "string", description: "Unique agent identifier (e.g., claude-code)" },
|
|
23478
|
+
agentName: { type: "string", description: "Human-readable agent name (e.g., Claude Code)" },
|
|
23479
|
+
currentTask: { type: "string", description: "Initial task description" },
|
|
23480
|
+
estimatedMinutesRemaining: { type: "number", description: "Estimated time to completion in minutes" }
|
|
23481
|
+
},
|
|
23482
|
+
required: ["cardId", "agentIdentifier", "agentName"]
|
|
23483
|
+
}
|
|
23484
|
+
},
|
|
23485
|
+
harmony_update_agent_progress: {
|
|
23486
|
+
description: "Update progress on an active agent session. Use to report progress percentage, current task, blockers, or status changes.",
|
|
23487
|
+
inputSchema: {
|
|
23488
|
+
type: "object",
|
|
23489
|
+
properties: {
|
|
23490
|
+
cardId: { type: "string", description: "Card ID with active session" },
|
|
23491
|
+
agentIdentifier: { type: "string", description: "Agent identifier" },
|
|
23492
|
+
agentName: { type: "string", description: "Agent name" },
|
|
23493
|
+
status: { type: "string", enum: ["working", "blocked", "paused"], description: "Current status" },
|
|
23494
|
+
progressPercent: { type: "number", description: "Progress percentage (0-100)" },
|
|
23495
|
+
currentTask: { type: "string", description: "What the agent is currently doing" },
|
|
23496
|
+
blockers: { type: "array", items: { type: "string" }, description: "List of blocking issues" },
|
|
23497
|
+
estimatedMinutesRemaining: { type: "number", description: "Updated time estimate" }
|
|
23498
|
+
},
|
|
23499
|
+
required: ["cardId", "agentIdentifier", "agentName"]
|
|
23500
|
+
}
|
|
23501
|
+
},
|
|
23502
|
+
harmony_end_agent_session: {
|
|
23503
|
+
description: "End an agent work session on a card. Call this when work is complete or paused.",
|
|
23504
|
+
inputSchema: {
|
|
23505
|
+
type: "object",
|
|
23506
|
+
properties: {
|
|
23507
|
+
cardId: { type: "string", description: "Card ID to end session on" },
|
|
23508
|
+
status: { type: "string", enum: ["completed", "paused"], description: "Final status (default: completed)" },
|
|
23509
|
+
progressPercent: { type: "number", description: "Final progress percentage" }
|
|
23510
|
+
},
|
|
23511
|
+
required: ["cardId"]
|
|
23512
|
+
}
|
|
23513
|
+
},
|
|
23514
|
+
harmony_get_agent_session: {
|
|
23515
|
+
description: "Get the current agent session for a card, including progress, status, and blockers.",
|
|
23516
|
+
inputSchema: {
|
|
23517
|
+
type: "object",
|
|
23518
|
+
properties: {
|
|
23519
|
+
cardId: { type: "string", description: "Card ID to check" },
|
|
23520
|
+
includeEnded: { type: "boolean", description: "Include ended sessions in history" }
|
|
23521
|
+
},
|
|
23522
|
+
required: ["cardId"]
|
|
23523
|
+
}
|
|
23454
23524
|
}
|
|
23455
23525
|
};
|
|
23456
23526
|
var RESOURCES = [
|
|
@@ -23767,6 +23837,49 @@ Include: cards moved recently, current in-progress items, blocked or high-priori
|
|
|
23767
23837
|
});
|
|
23768
23838
|
return { success: true, ...result };
|
|
23769
23839
|
}
|
|
23840
|
+
case "harmony_start_agent_session": {
|
|
23841
|
+
const cardId = exports_external.string().uuid().parse(args.cardId);
|
|
23842
|
+
const agentIdentifier = exports_external.string().min(1).parse(args.agentIdentifier);
|
|
23843
|
+
const agentName = exports_external.string().min(1).parse(args.agentName);
|
|
23844
|
+
const result = await client2.startAgentSession(cardId, {
|
|
23845
|
+
agentIdentifier,
|
|
23846
|
+
agentName,
|
|
23847
|
+
status: "working",
|
|
23848
|
+
currentTask: args.currentTask,
|
|
23849
|
+
estimatedMinutesRemaining: args.estimatedMinutesRemaining
|
|
23850
|
+
});
|
|
23851
|
+
return { success: true, ...result };
|
|
23852
|
+
}
|
|
23853
|
+
case "harmony_update_agent_progress": {
|
|
23854
|
+
const cardId = exports_external.string().uuid().parse(args.cardId);
|
|
23855
|
+
const agentIdentifier = exports_external.string().min(1).parse(args.agentIdentifier);
|
|
23856
|
+
const agentName = exports_external.string().min(1).parse(args.agentName);
|
|
23857
|
+
const result = await client2.updateAgentProgress(cardId, {
|
|
23858
|
+
agentIdentifier,
|
|
23859
|
+
agentName,
|
|
23860
|
+
status: args.status,
|
|
23861
|
+
progressPercent: args.progressPercent,
|
|
23862
|
+
currentTask: args.currentTask,
|
|
23863
|
+
blockers: args.blockers,
|
|
23864
|
+
estimatedMinutesRemaining: args.estimatedMinutesRemaining
|
|
23865
|
+
});
|
|
23866
|
+
return { success: true, ...result };
|
|
23867
|
+
}
|
|
23868
|
+
case "harmony_end_agent_session": {
|
|
23869
|
+
const cardId = exports_external.string().uuid().parse(args.cardId);
|
|
23870
|
+
const result = await client2.endAgentSession(cardId, {
|
|
23871
|
+
status: args.status || "completed",
|
|
23872
|
+
progressPercent: args.progressPercent
|
|
23873
|
+
});
|
|
23874
|
+
return { success: true, ...result };
|
|
23875
|
+
}
|
|
23876
|
+
case "harmony_get_agent_session": {
|
|
23877
|
+
const cardId = exports_external.string().uuid().parse(args.cardId);
|
|
23878
|
+
const result = await client2.getAgentSession(cardId, {
|
|
23879
|
+
includeEnded: args.includeEnded === true || args.includeEnded === "true"
|
|
23880
|
+
});
|
|
23881
|
+
return { success: true, ...result };
|
|
23882
|
+
}
|
|
23770
23883
|
default:
|
|
23771
23884
|
throw new Error(`Unknown tool: ${name}`);
|
|
23772
23885
|
}
|
package/dist/init.js
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
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 } 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
|
+
You are starting work on a Harmony card. Follow this workflow:
|
|
37
|
+
|
|
38
|
+
## Step 1: Find the Card
|
|
39
|
+
|
|
40
|
+
The user provided: $ARGUMENTS
|
|
41
|
+
|
|
42
|
+
Parse the card reference:
|
|
43
|
+
- If it starts with \`#\` followed by numbers (e.g., \`#42\`), use \`harmony_get_card_by_short_id\` with the number
|
|
44
|
+
- If it looks like a UUID, use \`harmony_get_card\` directly
|
|
45
|
+
- Otherwise, use \`harmony_search_cards\` to find by name
|
|
46
|
+
|
|
47
|
+
## Step 2: Move to In Progress
|
|
48
|
+
|
|
49
|
+
Once you have the card:
|
|
50
|
+
1. Get the board using \`harmony_get_board\` to find the "In Progress" column ID
|
|
51
|
+
2. Use \`harmony_move_card\` to move the card to "In Progress"
|
|
52
|
+
|
|
53
|
+
## Step 3: Add Agent Label
|
|
54
|
+
|
|
55
|
+
1. From the board response, find the label named "agent"
|
|
56
|
+
2. Use \`harmony_add_label_to_card\` to add it to the card
|
|
57
|
+
|
|
58
|
+
## Step 4: Start Agent Session
|
|
59
|
+
|
|
60
|
+
After adding the agent label, start tracking your work session:
|
|
61
|
+
1. Use \`harmony_start_agent_session\` with:
|
|
62
|
+
- \`cardId\`: The card ID
|
|
63
|
+
- \`agentIdentifier\`: Your agent identifier
|
|
64
|
+
- \`agentName\`: Your agent name
|
|
65
|
+
- \`currentTask\`: A brief description of what you're about to do
|
|
66
|
+
|
|
67
|
+
This enables the timer badge on the card to show your progress in real-time.
|
|
68
|
+
|
|
69
|
+
## Step 5: Display Card Details
|
|
70
|
+
|
|
71
|
+
Show the user:
|
|
72
|
+
- Card title and short ID
|
|
73
|
+
- Description (if any)
|
|
74
|
+
- Priority
|
|
75
|
+
- Current labels
|
|
76
|
+
- Due date (if set)
|
|
77
|
+
|
|
78
|
+
## Step 6: Implement the Solution
|
|
79
|
+
|
|
80
|
+
Based on the card's title and description:
|
|
81
|
+
1. Understand what needs to be done
|
|
82
|
+
2. Explore the codebase as needed
|
|
83
|
+
3. Implement the required changes
|
|
84
|
+
4. Test the changes
|
|
85
|
+
|
|
86
|
+
**During implementation, update your progress periodically:**
|
|
87
|
+
- Use \`harmony_update_agent_progress\` to report:
|
|
88
|
+
- \`progressPercent\`: Estimated completion (0-100)
|
|
89
|
+
- \`currentTask\`: What you're currently doing
|
|
90
|
+
- \`blockers\`: Any issues blocking progress (set \`status: "blocked"\` if blocked)
|
|
91
|
+
|
|
92
|
+
## Step 7: Complete Work
|
|
93
|
+
|
|
94
|
+
When implementation is complete:
|
|
95
|
+
1. Use \`harmony_end_agent_session\` with:
|
|
96
|
+
- \`cardId\`: The card ID
|
|
97
|
+
- \`status\`: "completed"
|
|
98
|
+
- \`progressPercent\`: 100
|
|
99
|
+
2. Use \`harmony_move_card\` to move the card to the "Review" column
|
|
100
|
+
3. Summarize what was accomplished
|
|
101
|
+
|
|
102
|
+
## Important Notes
|
|
103
|
+
- Always read the card description carefully before starting
|
|
104
|
+
- If the task is unclear, ask for clarification
|
|
105
|
+
- Make commits as appropriate during implementation
|
|
106
|
+
- The "agent" label indicates AI is working on the card
|
|
107
|
+
- Update progress at meaningful milestones, not constantly
|
|
108
|
+
- If you need to pause work, call \`harmony_end_agent_session\` with \`status: "paused"\`
|
|
109
|
+
`;
|
|
110
|
+
function ensureDir(dirPath) {
|
|
111
|
+
if (!existsSync(dirPath)) {
|
|
112
|
+
mkdirSync(dirPath, { recursive: true });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function writeFileIfNotExists(filePath, content, force) {
|
|
116
|
+
if (existsSync(filePath) && !force) {
|
|
117
|
+
return { created: false, skipped: true };
|
|
118
|
+
}
|
|
119
|
+
ensureDir(dirname(filePath));
|
|
120
|
+
writeFileSync(filePath, content, "utf-8");
|
|
121
|
+
return { created: true, skipped: false };
|
|
122
|
+
}
|
|
123
|
+
function mergeJsonFile(filePath, updates, force) {
|
|
124
|
+
if (!existsSync(filePath)) {
|
|
125
|
+
ensureDir(dirname(filePath));
|
|
126
|
+
writeFileSync(filePath, JSON.stringify(updates, null, 2), "utf-8");
|
|
127
|
+
return { created: true, skipped: false, merged: false };
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const existing = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
131
|
+
if (updates.mcpServers && existing.mcpServers) {
|
|
132
|
+
if (existing.mcpServers.harmony && !force) {
|
|
133
|
+
return { created: false, skipped: true, merged: false };
|
|
134
|
+
}
|
|
135
|
+
existing.mcpServers = { ...existing.mcpServers, ...updates.mcpServers };
|
|
136
|
+
} else {
|
|
137
|
+
Object.assign(existing, updates);
|
|
138
|
+
}
|
|
139
|
+
writeFileSync(filePath, JSON.stringify(existing, null, 2), "utf-8");
|
|
140
|
+
return { created: false, skipped: false, merged: true };
|
|
141
|
+
} catch {
|
|
142
|
+
if (force) {
|
|
143
|
+
writeFileSync(filePath, JSON.stringify(updates, null, 2), "utf-8");
|
|
144
|
+
return { created: true, skipped: false, merged: false };
|
|
145
|
+
}
|
|
146
|
+
return { created: false, skipped: true, merged: false };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function initClaude(cwd, force) {
|
|
150
|
+
const result = {
|
|
151
|
+
agent: "claude",
|
|
152
|
+
filesCreated: [],
|
|
153
|
+
filesSkipped: []
|
|
154
|
+
};
|
|
155
|
+
const commandContent = `---
|
|
156
|
+
description: Start working on a Harmony card (moves to In Progress, adds agent label)
|
|
157
|
+
argument-hint: <card-reference>
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
${HARMONY_WORKFLOW_PROMPT.replace("Your agent identifier", "claude-code").replace("Your agent name", "Claude Code")}
|
|
161
|
+
`;
|
|
162
|
+
const commandPath = join(cwd, ".claude", "commands", "hmy.md");
|
|
163
|
+
const { created, skipped } = writeFileIfNotExists(commandPath, commandContent, force);
|
|
164
|
+
if (created)
|
|
165
|
+
result.filesCreated.push(commandPath);
|
|
166
|
+
if (skipped)
|
|
167
|
+
result.filesSkipped.push(commandPath);
|
|
168
|
+
const globalConfigPath = join(homedir(), ".claude", "settings.json");
|
|
169
|
+
const mcpConfig = {
|
|
170
|
+
mcpServers: {
|
|
171
|
+
harmony: {
|
|
172
|
+
command: "harmony-mcp",
|
|
173
|
+
args: ["serve"]
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const configResult = mergeJsonFile(globalConfigPath, mcpConfig, force);
|
|
178
|
+
if (configResult.created || configResult.merged) {
|
|
179
|
+
result.filesCreated.push(globalConfigPath);
|
|
180
|
+
} else if (configResult.skipped) {
|
|
181
|
+
result.filesSkipped.push(globalConfigPath);
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
function initCodex(cwd, force) {
|
|
186
|
+
const result = {
|
|
187
|
+
agent: "codex",
|
|
188
|
+
filesCreated: [],
|
|
189
|
+
filesSkipped: []
|
|
190
|
+
};
|
|
191
|
+
const agentsContent = `# Harmony Integration
|
|
192
|
+
|
|
193
|
+
This project uses Harmony for task management. When working on tasks:
|
|
194
|
+
|
|
195
|
+
## Starting Work on a Card
|
|
196
|
+
|
|
197
|
+
When given a card reference (e.g., #42 or a card name), follow this workflow:
|
|
198
|
+
|
|
199
|
+
1. Use \`harmony_get_card_by_short_id\` or \`harmony_search_cards\` to find the card
|
|
200
|
+
2. Move the card to "In Progress" using \`harmony_move_card\`
|
|
201
|
+
3. Add the "agent" label using \`harmony_add_label_to_card\`
|
|
202
|
+
4. Start a session with \`harmony_start_agent_session\` (agentIdentifier: "codex", agentName: "OpenAI Codex")
|
|
203
|
+
5. Show the card details to the user
|
|
204
|
+
6. Implement the solution based on the card description
|
|
205
|
+
7. Update progress periodically with \`harmony_update_agent_progress\`
|
|
206
|
+
8. When done, call \`harmony_end_agent_session\` and move to "Review"
|
|
207
|
+
|
|
208
|
+
## Available Harmony Tools
|
|
209
|
+
|
|
210
|
+
- \`harmony_get_card\`, \`harmony_get_card_by_short_id\`, \`harmony_search_cards\` - Find cards
|
|
211
|
+
- \`harmony_move_card\` - Move cards between columns
|
|
212
|
+
- \`harmony_add_label_to_card\`, \`harmony_remove_label_from_card\` - Manage labels
|
|
213
|
+
- \`harmony_start_agent_session\`, \`harmony_update_agent_progress\`, \`harmony_end_agent_session\` - Track work
|
|
214
|
+
- \`harmony_get_board\` - Get board state
|
|
215
|
+
`;
|
|
216
|
+
const agentsPath = join(cwd, "AGENTS.md");
|
|
217
|
+
const { created: agentsCreated, skipped: agentsSkipped } = writeFileIfNotExists(agentsPath, agentsContent, force);
|
|
218
|
+
if (agentsCreated)
|
|
219
|
+
result.filesCreated.push(agentsPath);
|
|
220
|
+
if (agentsSkipped)
|
|
221
|
+
result.filesSkipped.push(agentsPath);
|
|
222
|
+
const promptContent = `---
|
|
223
|
+
name: hmy
|
|
224
|
+
description: Start working on a Harmony card
|
|
225
|
+
arguments:
|
|
226
|
+
- name: card
|
|
227
|
+
description: Card reference (#42, UUID, or name)
|
|
228
|
+
required: true
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "{{card}}").replace("Your agent identifier", "codex").replace("Your agent name", "OpenAI Codex")}
|
|
232
|
+
`;
|
|
233
|
+
const promptsDir = join(homedir(), ".codex", "prompts");
|
|
234
|
+
const promptPath = join(promptsDir, "hmy.md");
|
|
235
|
+
const { created: promptCreated, skipped: promptSkipped } = writeFileIfNotExists(promptPath, promptContent, force);
|
|
236
|
+
if (promptCreated)
|
|
237
|
+
result.filesCreated.push(promptPath);
|
|
238
|
+
if (promptSkipped)
|
|
239
|
+
result.filesSkipped.push(promptPath);
|
|
240
|
+
const configPath = join(homedir(), ".codex", "config.toml");
|
|
241
|
+
const mcpConfigSection = `
|
|
242
|
+
# Harmony MCP Server
|
|
243
|
+
[mcp_servers.harmony]
|
|
244
|
+
command = "harmony-mcp"
|
|
245
|
+
args = ["serve"]
|
|
246
|
+
`;
|
|
247
|
+
if (!existsSync(configPath)) {
|
|
248
|
+
ensureDir(dirname(configPath));
|
|
249
|
+
writeFileSync(configPath, mcpConfigSection, "utf-8");
|
|
250
|
+
result.filesCreated.push(configPath);
|
|
251
|
+
} else {
|
|
252
|
+
const existingConfig = readFileSync(configPath, "utf-8");
|
|
253
|
+
if (!existingConfig.includes("[mcp_servers.harmony]")) {
|
|
254
|
+
writeFileSync(configPath, existingConfig + `
|
|
255
|
+
` + mcpConfigSection, "utf-8");
|
|
256
|
+
result.filesCreated.push(configPath);
|
|
257
|
+
} else if (force) {
|
|
258
|
+
const updated = existingConfig.replace(/\[mcp_servers\.harmony\][\s\S]*?(?=\[|$)/, mcpConfigSection.trim() + `
|
|
259
|
+
|
|
260
|
+
`);
|
|
261
|
+
writeFileSync(configPath, updated, "utf-8");
|
|
262
|
+
result.filesCreated.push(configPath);
|
|
263
|
+
} else {
|
|
264
|
+
result.filesSkipped.push(configPath);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
function initCursor(cwd, force) {
|
|
270
|
+
const result = {
|
|
271
|
+
agent: "cursor",
|
|
272
|
+
filesCreated: [],
|
|
273
|
+
filesSkipped: []
|
|
274
|
+
};
|
|
275
|
+
const mcpConfigPath = join(cwd, ".cursor", "mcp.json");
|
|
276
|
+
const mcpConfig = {
|
|
277
|
+
mcpServers: {
|
|
278
|
+
harmony: {
|
|
279
|
+
command: "harmony-mcp",
|
|
280
|
+
args: ["serve"]
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
const mcpResult = mergeJsonFile(mcpConfigPath, mcpConfig, force);
|
|
285
|
+
if (mcpResult.created || mcpResult.merged) {
|
|
286
|
+
result.filesCreated.push(mcpConfigPath);
|
|
287
|
+
} else if (mcpResult.skipped) {
|
|
288
|
+
result.filesSkipped.push(mcpConfigPath);
|
|
289
|
+
}
|
|
290
|
+
const ruleContent = `---
|
|
291
|
+
description: Harmony card workflow rule
|
|
292
|
+
globs:
|
|
293
|
+
- "**/*"
|
|
294
|
+
alwaysApply: false
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
# Harmony Integration
|
|
298
|
+
|
|
299
|
+
When the user asks you to work on a Harmony card (references like #42, card names, or UUIDs):
|
|
300
|
+
|
|
301
|
+
${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Your agent identifier", "cursor").replace("Your agent name", "Cursor AI")}
|
|
302
|
+
`;
|
|
303
|
+
const rulePath = join(cwd, ".cursor", "rules", "harmony.mdc");
|
|
304
|
+
const { created: ruleCreated, skipped: ruleSkipped } = writeFileIfNotExists(rulePath, ruleContent, force);
|
|
305
|
+
if (ruleCreated)
|
|
306
|
+
result.filesCreated.push(rulePath);
|
|
307
|
+
if (ruleSkipped)
|
|
308
|
+
result.filesSkipped.push(rulePath);
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
311
|
+
function initWindsurf(cwd, force) {
|
|
312
|
+
const result = {
|
|
313
|
+
agent: "windsurf",
|
|
314
|
+
filesCreated: [],
|
|
315
|
+
filesSkipped: []
|
|
316
|
+
};
|
|
317
|
+
const globalMcpPath = join(homedir(), ".codeium", "windsurf", "mcp_config.json");
|
|
318
|
+
const mcpConfig = {
|
|
319
|
+
mcpServers: {
|
|
320
|
+
harmony: {
|
|
321
|
+
command: "harmony-mcp",
|
|
322
|
+
args: ["serve"],
|
|
323
|
+
disabled: false,
|
|
324
|
+
alwaysAllow: []
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
const mcpResult = mergeJsonFile(globalMcpPath, mcpConfig, force);
|
|
329
|
+
if (mcpResult.created || mcpResult.merged) {
|
|
330
|
+
result.filesCreated.push(globalMcpPath);
|
|
331
|
+
} else if (mcpResult.skipped) {
|
|
332
|
+
result.filesSkipped.push(globalMcpPath);
|
|
333
|
+
}
|
|
334
|
+
const ruleContent = `---
|
|
335
|
+
trigger: model_decision
|
|
336
|
+
description: Activate when user asks to work on a Harmony card (references like #42, card names, or task management)
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
# Harmony Card Workflow
|
|
340
|
+
|
|
341
|
+
When working on a Harmony card:
|
|
342
|
+
|
|
343
|
+
${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Your agent identifier", "windsurf").replace("Your agent name", "Windsurf AI")}
|
|
344
|
+
`;
|
|
345
|
+
const rulePath = join(cwd, ".windsurf", "rules", "harmony.md");
|
|
346
|
+
const { created: ruleCreated, skipped: ruleSkipped } = writeFileIfNotExists(rulePath, ruleContent, force);
|
|
347
|
+
if (ruleCreated)
|
|
348
|
+
result.filesCreated.push(rulePath);
|
|
349
|
+
if (ruleSkipped)
|
|
350
|
+
result.filesSkipped.push(rulePath);
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
function initHarmony(options = {}) {
|
|
354
|
+
const cwd = options.cwd || process.cwd();
|
|
355
|
+
const force = options.force || false;
|
|
356
|
+
const agents = options.agents || ["claude", "codex", "cursor", "windsurf"];
|
|
357
|
+
const results = [];
|
|
358
|
+
for (const agent of agents) {
|
|
359
|
+
try {
|
|
360
|
+
switch (agent) {
|
|
361
|
+
case "claude":
|
|
362
|
+
results.push(initClaude(cwd, force));
|
|
363
|
+
break;
|
|
364
|
+
case "codex":
|
|
365
|
+
results.push(initCodex(cwd, force));
|
|
366
|
+
break;
|
|
367
|
+
case "cursor":
|
|
368
|
+
results.push(initCursor(cwd, force));
|
|
369
|
+
break;
|
|
370
|
+
case "windsurf":
|
|
371
|
+
results.push(initWindsurf(cwd, force));
|
|
372
|
+
break;
|
|
373
|
+
default:
|
|
374
|
+
results.push({
|
|
375
|
+
agent,
|
|
376
|
+
filesCreated: [],
|
|
377
|
+
filesSkipped: [],
|
|
378
|
+
error: `Unknown agent: ${agent}`
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
} catch (error) {
|
|
382
|
+
results.push({
|
|
383
|
+
agent,
|
|
384
|
+
filesCreated: [],
|
|
385
|
+
filesSkipped: [],
|
|
386
|
+
error: error instanceof Error ? error.message : String(error)
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return results;
|
|
391
|
+
}
|
|
392
|
+
function detectAgents(cwd = process.cwd()) {
|
|
393
|
+
const detected = [];
|
|
394
|
+
if (existsSync(join(cwd, ".claude")) || existsSync(join(homedir(), ".claude"))) {
|
|
395
|
+
detected.push("claude");
|
|
396
|
+
}
|
|
397
|
+
if (existsSync(join(cwd, "AGENTS.md")) || existsSync(join(homedir(), ".codex"))) {
|
|
398
|
+
detected.push("codex");
|
|
399
|
+
}
|
|
400
|
+
if (existsSync(join(cwd, ".cursor"))) {
|
|
401
|
+
detected.push("cursor");
|
|
402
|
+
}
|
|
403
|
+
if (existsSync(join(cwd, ".windsurf")) || existsSync(join(cwd, ".windsurfrules")) || existsSync(join(homedir(), ".codeium", "windsurf"))) {
|
|
404
|
+
detected.push("windsurf");
|
|
405
|
+
}
|
|
406
|
+
return detected;
|
|
407
|
+
}
|
|
408
|
+
var SUPPORTED_AGENTS = ["claude", "codex", "cursor", "windsurf"];
|
|
409
|
+
export {
|
|
410
|
+
initHarmony,
|
|
411
|
+
detectAgents,
|
|
412
|
+
SUPPORTED_AGENTS
|
|
413
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harmony-mcp",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "MCP server for Harmony Kanban board - enables
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "MCP server for Harmony Kanban board - enables AI coding agents to manage your boards",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -23,18 +23,22 @@
|
|
|
23
23
|
"mcp",
|
|
24
24
|
"model-context-protocol",
|
|
25
25
|
"claude",
|
|
26
|
+
"codex",
|
|
27
|
+
"cursor",
|
|
28
|
+
"windsurf",
|
|
26
29
|
"kanban",
|
|
27
30
|
"harmony",
|
|
28
31
|
"ai",
|
|
29
32
|
"llm",
|
|
30
|
-
"project-management"
|
|
33
|
+
"project-management",
|
|
34
|
+
"coding-assistant"
|
|
31
35
|
],
|
|
32
36
|
"engines": {
|
|
33
37
|
"node": ">=18.0.0"
|
|
34
38
|
},
|
|
35
39
|
"scripts": {
|
|
36
|
-
"build": "bun build src/index.ts src/cli.ts --outdir dist --target node",
|
|
37
|
-
"build:bun": "bun build src/index.ts src/http.ts src/cli.ts --outdir dist --target bun",
|
|
40
|
+
"build": "bun build src/index.ts src/cli.ts src/init.ts --outdir dist --target node",
|
|
41
|
+
"build:bun": "bun build src/index.ts src/http.ts src/cli.ts src/init.ts --outdir dist --target bun",
|
|
38
42
|
"dev": "bun --watch src/index.ts",
|
|
39
43
|
"typecheck": "tsc --noEmit",
|
|
40
44
|
"prepublishOnly": "bun run build"
|