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 CHANGED
@@ -1,10 +1,13 @@
1
1
  # harmony-mcp
2
2
 
3
- MCP (Model Context Protocol) server for Harmony Kanban board. Enables Claude Code, Claude Desktop, and other LLMs to interact with your Harmony boards.
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
- Or run interactively:
35
+ ### 4. Initialize for Your AI Agents
33
36
 
34
37
  ```bash
35
- harmony-mcp configure
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
- ### 4. Add to Claude Code
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 manually to `~/.claude/settings.json`:
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
- That's it! Your API key is stored securely in `~/.harmony-mcp/config.json`.
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 join(homedir(), ".harmony-mcp");
25238
+ return join2(homedir2(), ".harmony-mcp");
24859
25239
  }
24860
25240
  function getConfigPath() {
24861
- return join(getConfigDir(), "config.json");
25241
+ return join2(getConfigDir(), "config.json");
24862
25242
  }
24863
25243
  function loadConfig() {
24864
25244
  const configPath = getConfigPath();
24865
- if (!existsSync(configPath)) {
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 = readFileSync(configPath, "utf-8");
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 (!existsSync(configDir)) {
24895
- mkdirSync(configDir, { recursive: true, mode: 448 });
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
- writeFileSync(configPath, JSON.stringify(newConfig, null, 2), { mode: 384 });
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",
4
- "description": "MCP server for Harmony Kanban board - enables Claude Code to manage your boards",
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"