harmony-mcp 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -15
- package/dist/cli.js +179 -28
- package/dist/index.js +88 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ MCP (Model Context Protocol) server for Harmony Kanban board. Enables AI coding
|
|
|
8
8
|
- **Card Linking** - create relationships between cards (blocks, relates_to, duplicates, is_part_of)
|
|
9
9
|
- **Prompt Builder** - generate AI-ready prompts from cards with context
|
|
10
10
|
- **Agent Session Tracking** - track work progress with timer badges
|
|
11
|
+
- **Auto-Assignment** - automatically assign cards to you when starting agent sessions
|
|
11
12
|
- **Multi-Agent Support** - works with Claude Code, Codex, Cursor, Windsurf
|
|
12
13
|
- **One-Command Setup** - auto-configure all supported agents
|
|
13
14
|
- **Natural Language Processing** via voice-nlu edge function
|
|
@@ -32,6 +33,9 @@ npm install -g harmony-mcp
|
|
|
32
33
|
|
|
33
34
|
```bash
|
|
34
35
|
harmony-mcp configure --api-key hmy_your_key_here
|
|
36
|
+
|
|
37
|
+
# Optional: Set your email for auto-assignment
|
|
38
|
+
harmony-mcp configure --api-key hmy_your_key_here --user-email you@example.com
|
|
35
39
|
```
|
|
36
40
|
|
|
37
41
|
### 4. Initialize for Your AI Agents
|
|
@@ -86,23 +90,30 @@ When you start working on a card (e.g., `/hmy #42`):
|
|
|
86
90
|
1. **Find** - Locates the card by short ID, UUID, or name
|
|
87
91
|
2. **Move** - Moves the card to "In Progress" column
|
|
88
92
|
3. **Label** - Adds the "agent" label to indicate AI is working
|
|
89
|
-
4. **
|
|
90
|
-
5. **
|
|
91
|
-
6. **
|
|
93
|
+
4. **Assign** - Auto-assigns the card to you (if `userEmail` is configured)
|
|
94
|
+
5. **Track** - Starts a session timer visible in the UI
|
|
95
|
+
6. **Implement** - Work on the task with progress updates
|
|
96
|
+
7. **Complete** - Move to "Review" when done
|
|
92
97
|
|
|
93
98
|
## CLI Commands
|
|
94
99
|
|
|
95
100
|
```bash
|
|
96
|
-
harmony-mcp configure
|
|
97
|
-
harmony-mcp
|
|
98
|
-
harmony-mcp init
|
|
99
|
-
harmony-mcp init --
|
|
100
|
-
harmony-mcp init --
|
|
101
|
-
harmony-mcp
|
|
102
|
-
harmony-mcp
|
|
103
|
-
harmony-mcp
|
|
104
|
-
harmony-mcp
|
|
105
|
-
harmony-mcp
|
|
101
|
+
harmony-mcp configure # Set up API key (interactive)
|
|
102
|
+
harmony-mcp configure -k KEY -e EMAIL # Set up with API key and email
|
|
103
|
+
harmony-mcp init # Initialize for AI agents (interactive)
|
|
104
|
+
harmony-mcp init --all # Configure all supported agents
|
|
105
|
+
harmony-mcp init --detect # Auto-detect and configure installed agents
|
|
106
|
+
harmony-mcp init --agent X Y # Configure specific agents
|
|
107
|
+
harmony-mcp init -w WS -p PROJ # Initialize with local workspace/project context
|
|
108
|
+
harmony-mcp status # Show current config (global and local)
|
|
109
|
+
harmony-mcp reset # Clear configuration
|
|
110
|
+
harmony-mcp set-workspace ID # Set active workspace (global)
|
|
111
|
+
harmony-mcp set-workspace ID -l # Set active workspace (local project)
|
|
112
|
+
harmony-mcp set-project ID # Set active project (global)
|
|
113
|
+
harmony-mcp set-project ID -l # Set active project (local project)
|
|
114
|
+
harmony-mcp set-user-email EMAIL # Set email for auto-assignment
|
|
115
|
+
harmony-mcp clear-user-email # Disable auto-assignment
|
|
116
|
+
harmony-mcp serve # Start MCP server
|
|
106
117
|
```
|
|
107
118
|
|
|
108
119
|
## Available Tools
|
|
@@ -233,17 +244,97 @@ curl -X GET "https://gethmy.com/api/workspaces" \
|
|
|
233
244
|
|
|
234
245
|
## Configuration
|
|
235
246
|
|
|
236
|
-
|
|
247
|
+
### Global Configuration
|
|
248
|
+
|
|
249
|
+
Your global configuration is stored in `~/.harmony-mcp/config.json`:
|
|
237
250
|
|
|
238
251
|
```json
|
|
239
252
|
{
|
|
240
253
|
"apiKey": "hmy_...",
|
|
241
254
|
"apiUrl": "https://gethmy.com/api",
|
|
242
255
|
"activeWorkspaceId": null,
|
|
243
|
-
"activeProjectId": null
|
|
256
|
+
"activeProjectId": null,
|
|
257
|
+
"userEmail": "you@example.com"
|
|
244
258
|
}
|
|
245
259
|
```
|
|
246
260
|
|
|
261
|
+
### Auto-Assignment
|
|
262
|
+
|
|
263
|
+
When `userEmail` is configured, cards are automatically assigned to you when you start an agent session (e.g., via `/hmy #42`). This helps track who is working on what.
|
|
264
|
+
|
|
265
|
+
To enable auto-assignment:
|
|
266
|
+
```bash
|
|
267
|
+
harmony-mcp set-user-email you@example.com
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
To disable auto-assignment:
|
|
271
|
+
```bash
|
|
272
|
+
harmony-mcp clear-user-email
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
The email must match your Harmony account email to work correctly.
|
|
276
|
+
|
|
277
|
+
### Local Project Configuration
|
|
278
|
+
|
|
279
|
+
For multi-project workflows, you can set project-specific context using local configuration. This is stored in `.harmony-mcp.json` in your project root:
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"workspaceId": "uuid-here",
|
|
284
|
+
"projectId": "uuid-here"
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Priority**: Local config overrides global config for workspace and project context.
|
|
289
|
+
|
|
290
|
+
**Security**: API key is never stored locally (only in global config) to prevent accidental commits.
|
|
291
|
+
|
|
292
|
+
#### Setting Local Context
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
# During initialization
|
|
296
|
+
harmony-mcp init --workspace <ws-id> --project <proj-id>
|
|
297
|
+
|
|
298
|
+
# Or separately
|
|
299
|
+
harmony-mcp set-workspace <id> --local
|
|
300
|
+
harmony-mcp set-project <id> --local
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
#### Viewing Configuration
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
harmony-mcp status
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Example output:
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
Status: Configured
|
|
313
|
+
API Key: hmy_abc1...
|
|
314
|
+
API URL: https://gethmy.com/api
|
|
315
|
+
User Email: y***@example.com
|
|
316
|
+
|
|
317
|
+
Global Context:
|
|
318
|
+
Workspace: <global-ws-id>
|
|
319
|
+
Project: <global-proj-id>
|
|
320
|
+
|
|
321
|
+
Local Context (.harmony-mcp.json):
|
|
322
|
+
Workspace: <local-ws-id>
|
|
323
|
+
Project: <local-proj-id>
|
|
324
|
+
|
|
325
|
+
Active (effective):
|
|
326
|
+
Workspace: <local-ws-id> ← local
|
|
327
|
+
Project: <local-proj-id> ← local
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
#### Recommended .gitignore
|
|
331
|
+
|
|
332
|
+
Add this to prevent accidentally committing local config:
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
.harmony-mcp.json
|
|
336
|
+
```
|
|
337
|
+
|
|
247
338
|
## Architecture
|
|
248
339
|
|
|
249
340
|
```
|
package/dist/cli.js
CHANGED
|
@@ -25234,12 +25234,16 @@ import { homedir as homedir2 } from "node:os";
|
|
|
25234
25234
|
import { join as join2 } from "node:path";
|
|
25235
25235
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
25236
25236
|
var DEFAULT_API_URL = "https://gethmy.com/api";
|
|
25237
|
+
var LOCAL_CONFIG_FILENAME = ".harmony-mcp.json";
|
|
25237
25238
|
function getConfigDir() {
|
|
25238
25239
|
return join2(homedir2(), ".harmony-mcp");
|
|
25239
25240
|
}
|
|
25240
25241
|
function getConfigPath() {
|
|
25241
25242
|
return join2(getConfigDir(), "config.json");
|
|
25242
25243
|
}
|
|
25244
|
+
function getLocalConfigPath(cwd) {
|
|
25245
|
+
return join2(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
|
|
25246
|
+
}
|
|
25243
25247
|
function loadConfig() {
|
|
25244
25248
|
const configPath = getConfigPath();
|
|
25245
25249
|
if (!existsSync2(configPath)) {
|
|
@@ -25247,7 +25251,8 @@ function loadConfig() {
|
|
|
25247
25251
|
apiKey: null,
|
|
25248
25252
|
apiUrl: DEFAULT_API_URL,
|
|
25249
25253
|
activeWorkspaceId: null,
|
|
25250
|
-
activeProjectId: null
|
|
25254
|
+
activeProjectId: null,
|
|
25255
|
+
userEmail: null
|
|
25251
25256
|
};
|
|
25252
25257
|
}
|
|
25253
25258
|
try {
|
|
@@ -25257,14 +25262,16 @@ function loadConfig() {
|
|
|
25257
25262
|
apiKey: config2.apiKey || null,
|
|
25258
25263
|
apiUrl: config2.apiUrl || DEFAULT_API_URL,
|
|
25259
25264
|
activeWorkspaceId: config2.activeWorkspaceId || null,
|
|
25260
|
-
activeProjectId: config2.activeProjectId || null
|
|
25265
|
+
activeProjectId: config2.activeProjectId || null,
|
|
25266
|
+
userEmail: config2.userEmail || null
|
|
25261
25267
|
};
|
|
25262
25268
|
} catch {
|
|
25263
25269
|
return {
|
|
25264
25270
|
apiKey: null,
|
|
25265
25271
|
apiUrl: DEFAULT_API_URL,
|
|
25266
25272
|
activeWorkspaceId: null,
|
|
25267
|
-
activeProjectId: null
|
|
25273
|
+
activeProjectId: null,
|
|
25274
|
+
userEmail: null
|
|
25268
25275
|
};
|
|
25269
25276
|
}
|
|
25270
25277
|
}
|
|
@@ -25278,6 +25285,39 @@ function saveConfig(config2) {
|
|
|
25278
25285
|
const newConfig = { ...existingConfig, ...config2 };
|
|
25279
25286
|
writeFileSync2(configPath, JSON.stringify(newConfig, null, 2), { mode: 384 });
|
|
25280
25287
|
}
|
|
25288
|
+
function loadLocalConfig(cwd) {
|
|
25289
|
+
const localConfigPath = getLocalConfigPath(cwd);
|
|
25290
|
+
if (!existsSync2(localConfigPath)) {
|
|
25291
|
+
return null;
|
|
25292
|
+
}
|
|
25293
|
+
try {
|
|
25294
|
+
const data = readFileSync2(localConfigPath, "utf-8");
|
|
25295
|
+
const config2 = JSON.parse(data);
|
|
25296
|
+
return {
|
|
25297
|
+
workspaceId: config2.workspaceId || null,
|
|
25298
|
+
projectId: config2.projectId || null
|
|
25299
|
+
};
|
|
25300
|
+
} catch {
|
|
25301
|
+
return null;
|
|
25302
|
+
}
|
|
25303
|
+
}
|
|
25304
|
+
function saveLocalConfig(config2, cwd) {
|
|
25305
|
+
const localConfigPath = getLocalConfigPath(cwd);
|
|
25306
|
+
const existingConfig = loadLocalConfig(cwd) || {
|
|
25307
|
+
workspaceId: null,
|
|
25308
|
+
projectId: null
|
|
25309
|
+
};
|
|
25310
|
+
const newConfig = { ...existingConfig, ...config2 };
|
|
25311
|
+
const cleanConfig = {};
|
|
25312
|
+
if (newConfig.workspaceId)
|
|
25313
|
+
cleanConfig.workspaceId = newConfig.workspaceId;
|
|
25314
|
+
if (newConfig.projectId)
|
|
25315
|
+
cleanConfig.projectId = newConfig.projectId;
|
|
25316
|
+
writeFileSync2(localConfigPath, JSON.stringify(cleanConfig, null, 2));
|
|
25317
|
+
}
|
|
25318
|
+
function hasLocalConfig(cwd) {
|
|
25319
|
+
return existsSync2(getLocalConfigPath(cwd));
|
|
25320
|
+
}
|
|
25281
25321
|
function getApiKey() {
|
|
25282
25322
|
const config2 = loadConfig();
|
|
25283
25323
|
if (!config2.apiKey) {
|
|
@@ -25290,16 +25330,39 @@ function getApiUrl() {
|
|
|
25290
25330
|
const config2 = loadConfig();
|
|
25291
25331
|
return config2.apiUrl;
|
|
25292
25332
|
}
|
|
25293
|
-
function
|
|
25294
|
-
|
|
25333
|
+
function getUserEmail() {
|
|
25334
|
+
const config2 = loadConfig();
|
|
25335
|
+
return config2.userEmail;
|
|
25336
|
+
}
|
|
25337
|
+
function setUserEmail(email2) {
|
|
25338
|
+
saveConfig({ userEmail: email2 });
|
|
25295
25339
|
}
|
|
25296
|
-
function
|
|
25297
|
-
|
|
25340
|
+
function setActiveWorkspace(workspaceId, options) {
|
|
25341
|
+
if (options?.local) {
|
|
25342
|
+
saveLocalConfig({ workspaceId }, options.cwd);
|
|
25343
|
+
} else {
|
|
25344
|
+
saveConfig({ activeWorkspaceId: workspaceId });
|
|
25345
|
+
}
|
|
25346
|
+
}
|
|
25347
|
+
function setActiveProject(projectId, options) {
|
|
25348
|
+
if (options?.local) {
|
|
25349
|
+
saveLocalConfig({ projectId }, options.cwd);
|
|
25350
|
+
} else {
|
|
25351
|
+
saveConfig({ activeProjectId: projectId });
|
|
25352
|
+
}
|
|
25298
25353
|
}
|
|
25299
|
-
function getActiveWorkspaceId() {
|
|
25354
|
+
function getActiveWorkspaceId(cwd) {
|
|
25355
|
+
const localConfig = loadLocalConfig(cwd);
|
|
25356
|
+
if (localConfig?.workspaceId) {
|
|
25357
|
+
return localConfig.workspaceId;
|
|
25358
|
+
}
|
|
25300
25359
|
return loadConfig().activeWorkspaceId;
|
|
25301
25360
|
}
|
|
25302
|
-
function getActiveProjectId() {
|
|
25361
|
+
function getActiveProjectId(cwd) {
|
|
25362
|
+
const localConfig = loadLocalConfig(cwd);
|
|
25363
|
+
if (localConfig?.projectId) {
|
|
25364
|
+
return localConfig.projectId;
|
|
25365
|
+
}
|
|
25303
25366
|
return loadConfig().activeProjectId;
|
|
25304
25367
|
}
|
|
25305
25368
|
function isConfigured() {
|
|
@@ -26468,6 +26531,21 @@ Include: cards moved recently, current in-progress items, blocked or high-priori
|
|
|
26468
26531
|
const cardId = exports_external.string().uuid().parse(args.cardId);
|
|
26469
26532
|
const agentIdentifier = exports_external.string().min(1).parse(args.agentIdentifier);
|
|
26470
26533
|
const agentName = exports_external.string().min(1).parse(args.agentName);
|
|
26534
|
+
let assignedTo = null;
|
|
26535
|
+
const userEmail = getUserEmail();
|
|
26536
|
+
if (userEmail) {
|
|
26537
|
+
try {
|
|
26538
|
+
const workspaceId = getActiveWorkspaceId();
|
|
26539
|
+
if (workspaceId) {
|
|
26540
|
+
const { members } = await client2.getWorkspaceMembers(workspaceId);
|
|
26541
|
+
const user = members.find((m) => m.email === userEmail);
|
|
26542
|
+
if (user) {
|
|
26543
|
+
await client2.updateCard(cardId, { assigneeId: user.id });
|
|
26544
|
+
assignedTo = user.email;
|
|
26545
|
+
}
|
|
26546
|
+
}
|
|
26547
|
+
} catch {}
|
|
26548
|
+
}
|
|
26471
26549
|
const result = await client2.startAgentSession(cardId, {
|
|
26472
26550
|
agentIdentifier,
|
|
26473
26551
|
agentName,
|
|
@@ -26475,7 +26553,7 @@ Include: cards moved recently, current in-progress items, blocked or high-priori
|
|
|
26475
26553
|
currentTask: args.currentTask,
|
|
26476
26554
|
estimatedMinutesRemaining: args.estimatedMinutesRemaining
|
|
26477
26555
|
});
|
|
26478
|
-
return { success: true, ...result };
|
|
26556
|
+
return { success: true, assignedTo, ...result };
|
|
26479
26557
|
}
|
|
26480
26558
|
case "harmony_update_agent_progress": {
|
|
26481
26559
|
const cardId = exports_external.string().uuid().parse(args.cardId);
|
|
@@ -26590,10 +26668,11 @@ program.command("serve").description("Start the MCP server (stdio transport)").a
|
|
|
26590
26668
|
const server = new HarmonyMCPServer;
|
|
26591
26669
|
await server.run();
|
|
26592
26670
|
});
|
|
26593
|
-
program.command("configure").description("Configure the MCP server with your Harmony API key").option("-k, --api-key <key>", "API key (generate at gethmy.com → Settings → API Keys)").option("-u, --api-url <url>", "API URL (optional, for self-hosted instances)").action(async (options) => {
|
|
26671
|
+
program.command("configure").description("Configure the MCP server with your Harmony API key").option("-k, --api-key <key>", "API key (generate at gethmy.com → Settings → API Keys)").option("-u, --api-url <url>", "API URL (optional, for self-hosted instances)").option("-e, --user-email <email>", "Your email for auto-assignment when starting agent sessions").action(async (options) => {
|
|
26594
26672
|
try {
|
|
26595
26673
|
let apiKey = options.apiKey;
|
|
26596
26674
|
let apiUrl = options.apiUrl;
|
|
26675
|
+
let userEmail = options.userEmail;
|
|
26597
26676
|
if (!apiKey) {
|
|
26598
26677
|
console.log(`Configure Harmony MCP Server
|
|
26599
26678
|
`);
|
|
@@ -26610,10 +26689,16 @@ program.command("configure").description("Configure the MCP server with your Har
|
|
|
26610
26689
|
if (apiUrl) {
|
|
26611
26690
|
config2.apiUrl = apiUrl;
|
|
26612
26691
|
}
|
|
26692
|
+
if (userEmail) {
|
|
26693
|
+
config2.userEmail = userEmail;
|
|
26694
|
+
}
|
|
26613
26695
|
saveConfig(config2);
|
|
26614
26696
|
console.log(`
|
|
26615
26697
|
Configuration saved successfully!`);
|
|
26616
26698
|
console.log(`Config file: ${getConfigPath()}`);
|
|
26699
|
+
if (userEmail) {
|
|
26700
|
+
console.log(`User email: ${userEmail}`);
|
|
26701
|
+
}
|
|
26617
26702
|
console.log(`
|
|
26618
26703
|
You can now use the MCP server with Claude Code.`);
|
|
26619
26704
|
console.log(`Add this to your ~/.claude/settings.json:
|
|
@@ -26634,19 +26719,43 @@ You can now use the MCP server with Claude Code.`);
|
|
|
26634
26719
|
}
|
|
26635
26720
|
});
|
|
26636
26721
|
program.command("status").description("Show configuration status").action(() => {
|
|
26637
|
-
const
|
|
26722
|
+
const globalConfig2 = loadConfig();
|
|
26723
|
+
const localConfig = loadLocalConfig();
|
|
26724
|
+
const hasLocal = hasLocalConfig();
|
|
26725
|
+
const maskEmail = (email2) => {
|
|
26726
|
+
const [local, domain] = email2.split("@");
|
|
26727
|
+
if (!domain)
|
|
26728
|
+
return email2;
|
|
26729
|
+
return `${local[0]}***@${domain}`;
|
|
26730
|
+
};
|
|
26638
26731
|
if (isConfigured()) {
|
|
26639
26732
|
console.log("Status: Configured");
|
|
26640
|
-
console.log(`API Key: ${
|
|
26641
|
-
console.log(`API URL: ${
|
|
26642
|
-
|
|
26643
|
-
|
|
26644
|
-
|
|
26645
|
-
|
|
26646
|
-
|
|
26733
|
+
console.log(`API Key: ${globalConfig2.apiKey?.slice(0, 8)}...`);
|
|
26734
|
+
console.log(`API URL: ${globalConfig2.apiUrl}`);
|
|
26735
|
+
console.log(`User Email: ${globalConfig2.userEmail ? maskEmail(globalConfig2.userEmail) : "(not set)"}`);
|
|
26736
|
+
console.log(`
|
|
26737
|
+
Global Context:`);
|
|
26738
|
+
console.log(` Workspace: ${globalConfig2.activeWorkspaceId || "(not set)"}`);
|
|
26739
|
+
console.log(` Project: ${globalConfig2.activeProjectId || "(not set)"}`);
|
|
26740
|
+
if (hasLocal) {
|
|
26741
|
+
console.log(`
|
|
26742
|
+
Local Context (${getLocalConfigPath()}):`);
|
|
26743
|
+
console.log(` Workspace: ${localConfig?.workspaceId || "(not set)"}`);
|
|
26744
|
+
console.log(` Project: ${localConfig?.projectId || "(not set)"}`);
|
|
26647
26745
|
}
|
|
26746
|
+
const effectiveWorkspace = getActiveWorkspaceId();
|
|
26747
|
+
const effectiveProject = getActiveProjectId();
|
|
26748
|
+
console.log(`
|
|
26749
|
+
Active (effective):`);
|
|
26750
|
+
const wsSource = localConfig?.workspaceId ? "local" : globalConfig2.activeWorkspaceId ? "global" : "";
|
|
26751
|
+
const projSource = localConfig?.projectId ? "local" : globalConfig2.activeProjectId ? "global" : "";
|
|
26752
|
+
console.log(` Workspace: ${effectiveWorkspace || "(not set)"}${wsSource ? ` ← ${wsSource}` : ""}`);
|
|
26753
|
+
console.log(` Project: ${effectiveProject || "(not set)"}${projSource ? ` ← ${projSource}` : ""}`);
|
|
26648
26754
|
console.log(`
|
|
26649
|
-
|
|
26755
|
+
Global config: ${getConfigPath()}`);
|
|
26756
|
+
if (hasLocal) {
|
|
26757
|
+
console.log(`Local config: ${getLocalConfigPath()}`);
|
|
26758
|
+
}
|
|
26650
26759
|
} else {
|
|
26651
26760
|
console.log("Status: Not configured");
|
|
26652
26761
|
console.log(`
|
|
@@ -26658,19 +26767,48 @@ program.command("reset").description("Remove stored configuration").action(() =>
|
|
|
26658
26767
|
saveConfig({
|
|
26659
26768
|
apiKey: null,
|
|
26660
26769
|
activeWorkspaceId: null,
|
|
26661
|
-
activeProjectId: null
|
|
26770
|
+
activeProjectId: null,
|
|
26771
|
+
userEmail: null
|
|
26662
26772
|
});
|
|
26663
26773
|
console.log("Configuration reset successfully");
|
|
26664
26774
|
});
|
|
26665
|
-
program.command("set-workspace <workspaceId>").description("Set the active workspace context").action((workspaceId) => {
|
|
26666
|
-
|
|
26667
|
-
|
|
26775
|
+
program.command("set-workspace <workspaceId>").description("Set the active workspace context").option("-l, --local", "Save to local project config (.harmony-mcp.json) instead of global").action((workspaceId, options) => {
|
|
26776
|
+
const isLocal = options.local;
|
|
26777
|
+
setActiveWorkspace(workspaceId, { local: isLocal });
|
|
26778
|
+
if (isLocal) {
|
|
26779
|
+
console.log(`Local workspace set to: ${workspaceId}`);
|
|
26780
|
+
console.log(`Config file: ${getLocalConfigPath()}`);
|
|
26781
|
+
} else {
|
|
26782
|
+
console.log(`Global workspace set to: ${workspaceId}`);
|
|
26783
|
+
}
|
|
26668
26784
|
});
|
|
26669
|
-
program.command("set-project <projectId>").description("Set the active project context").action((projectId) => {
|
|
26670
|
-
|
|
26671
|
-
|
|
26785
|
+
program.command("set-project <projectId>").description("Set the active project context").option("-l, --local", "Save to local project config (.harmony-mcp.json) instead of global").action((projectId, options) => {
|
|
26786
|
+
const isLocal = options.local;
|
|
26787
|
+
setActiveProject(projectId, { local: isLocal });
|
|
26788
|
+
if (isLocal) {
|
|
26789
|
+
console.log(`Local project set to: ${projectId}`);
|
|
26790
|
+
console.log(`Config file: ${getLocalConfigPath()}`);
|
|
26791
|
+
} else {
|
|
26792
|
+
console.log(`Global project set to: ${projectId}`);
|
|
26793
|
+
}
|
|
26794
|
+
});
|
|
26795
|
+
program.command("set-user-email <email>").description("Set your email for auto-assignment when starting agent sessions").action((email2) => {
|
|
26796
|
+
if (!email2.includes("@") || !email2.includes(".")) {
|
|
26797
|
+
console.error("Error: Invalid email format");
|
|
26798
|
+
process.exit(1);
|
|
26799
|
+
}
|
|
26800
|
+
setUserEmail(email2);
|
|
26801
|
+
console.log(`User email set to: ${email2}`);
|
|
26802
|
+
console.log(`
|
|
26803
|
+
Cards will be automatically assigned to you when starting agent sessions.`);
|
|
26672
26804
|
});
|
|
26673
|
-
program.command("
|
|
26805
|
+
program.command("clear-user-email").description("Clear the configured user email (disables auto-assignment)").action(() => {
|
|
26806
|
+
setUserEmail(null);
|
|
26807
|
+
console.log("User email cleared");
|
|
26808
|
+
console.log(`
|
|
26809
|
+
Auto-assignment is now disabled.`);
|
|
26810
|
+
});
|
|
26811
|
+
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)").option("-w, --workspace <id>", "Set local workspace context for this project").option("-p, --project <id>", "Set local project context for this project").action(async (options) => {
|
|
26674
26812
|
console.log(`Harmony MCP Initialization
|
|
26675
26813
|
`);
|
|
26676
26814
|
let agents = [];
|
|
@@ -26768,6 +26906,19 @@ ${result.agent.toUpperCase()}:`);
|
|
|
26768
26906
|
console.log(" No changes made");
|
|
26769
26907
|
}
|
|
26770
26908
|
}
|
|
26909
|
+
if (options.workspace || options.project) {
|
|
26910
|
+
console.log(`
|
|
26911
|
+
LOCAL CONTEXT:`);
|
|
26912
|
+
if (options.workspace) {
|
|
26913
|
+
saveLocalConfig({ workspaceId: options.workspace }, cwd);
|
|
26914
|
+
console.log(` ✓ Workspace: ${options.workspace}`);
|
|
26915
|
+
}
|
|
26916
|
+
if (options.project) {
|
|
26917
|
+
saveLocalConfig({ projectId: options.project }, cwd);
|
|
26918
|
+
console.log(` ✓ Project: ${options.project}`);
|
|
26919
|
+
}
|
|
26920
|
+
console.log(` Config file: ${getLocalConfigPath(cwd)}`);
|
|
26921
|
+
}
|
|
26771
26922
|
console.log(`
|
|
26772
26923
|
` + "─".repeat(60));
|
|
26773
26924
|
if (!isConfigured()) {
|
package/dist/index.js
CHANGED
|
@@ -22996,12 +22996,16 @@ import { homedir } from "node:os";
|
|
|
22996
22996
|
import { join } from "node:path";
|
|
22997
22997
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
22998
22998
|
var DEFAULT_API_URL = "https://gethmy.com/api";
|
|
22999
|
+
var LOCAL_CONFIG_FILENAME = ".harmony-mcp.json";
|
|
22999
23000
|
function getConfigDir() {
|
|
23000
23001
|
return join(homedir(), ".harmony-mcp");
|
|
23001
23002
|
}
|
|
23002
23003
|
function getConfigPath() {
|
|
23003
23004
|
return join(getConfigDir(), "config.json");
|
|
23004
23005
|
}
|
|
23006
|
+
function getLocalConfigPath(cwd) {
|
|
23007
|
+
return join(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
|
|
23008
|
+
}
|
|
23005
23009
|
function loadConfig() {
|
|
23006
23010
|
const configPath = getConfigPath();
|
|
23007
23011
|
if (!existsSync(configPath)) {
|
|
@@ -23009,7 +23013,8 @@ function loadConfig() {
|
|
|
23009
23013
|
apiKey: null,
|
|
23010
23014
|
apiUrl: DEFAULT_API_URL,
|
|
23011
23015
|
activeWorkspaceId: null,
|
|
23012
|
-
activeProjectId: null
|
|
23016
|
+
activeProjectId: null,
|
|
23017
|
+
userEmail: null
|
|
23013
23018
|
};
|
|
23014
23019
|
}
|
|
23015
23020
|
try {
|
|
@@ -23019,14 +23024,16 @@ function loadConfig() {
|
|
|
23019
23024
|
apiKey: config2.apiKey || null,
|
|
23020
23025
|
apiUrl: config2.apiUrl || DEFAULT_API_URL,
|
|
23021
23026
|
activeWorkspaceId: config2.activeWorkspaceId || null,
|
|
23022
|
-
activeProjectId: config2.activeProjectId || null
|
|
23027
|
+
activeProjectId: config2.activeProjectId || null,
|
|
23028
|
+
userEmail: config2.userEmail || null
|
|
23023
23029
|
};
|
|
23024
23030
|
} catch {
|
|
23025
23031
|
return {
|
|
23026
23032
|
apiKey: null,
|
|
23027
23033
|
apiUrl: DEFAULT_API_URL,
|
|
23028
23034
|
activeWorkspaceId: null,
|
|
23029
|
-
activeProjectId: null
|
|
23035
|
+
activeProjectId: null,
|
|
23036
|
+
userEmail: null
|
|
23030
23037
|
};
|
|
23031
23038
|
}
|
|
23032
23039
|
}
|
|
@@ -23040,6 +23047,39 @@ function saveConfig(config2) {
|
|
|
23040
23047
|
const newConfig = { ...existingConfig, ...config2 };
|
|
23041
23048
|
writeFileSync(configPath, JSON.stringify(newConfig, null, 2), { mode: 384 });
|
|
23042
23049
|
}
|
|
23050
|
+
function loadLocalConfig(cwd) {
|
|
23051
|
+
const localConfigPath = getLocalConfigPath(cwd);
|
|
23052
|
+
if (!existsSync(localConfigPath)) {
|
|
23053
|
+
return null;
|
|
23054
|
+
}
|
|
23055
|
+
try {
|
|
23056
|
+
const data = readFileSync(localConfigPath, "utf-8");
|
|
23057
|
+
const config2 = JSON.parse(data);
|
|
23058
|
+
return {
|
|
23059
|
+
workspaceId: config2.workspaceId || null,
|
|
23060
|
+
projectId: config2.projectId || null
|
|
23061
|
+
};
|
|
23062
|
+
} catch {
|
|
23063
|
+
return null;
|
|
23064
|
+
}
|
|
23065
|
+
}
|
|
23066
|
+
function saveLocalConfig(config2, cwd) {
|
|
23067
|
+
const localConfigPath = getLocalConfigPath(cwd);
|
|
23068
|
+
const existingConfig = loadLocalConfig(cwd) || {
|
|
23069
|
+
workspaceId: null,
|
|
23070
|
+
projectId: null
|
|
23071
|
+
};
|
|
23072
|
+
const newConfig = { ...existingConfig, ...config2 };
|
|
23073
|
+
const cleanConfig = {};
|
|
23074
|
+
if (newConfig.workspaceId)
|
|
23075
|
+
cleanConfig.workspaceId = newConfig.workspaceId;
|
|
23076
|
+
if (newConfig.projectId)
|
|
23077
|
+
cleanConfig.projectId = newConfig.projectId;
|
|
23078
|
+
writeFileSync(localConfigPath, JSON.stringify(cleanConfig, null, 2));
|
|
23079
|
+
}
|
|
23080
|
+
function hasLocalConfig(cwd) {
|
|
23081
|
+
return existsSync(getLocalConfigPath(cwd));
|
|
23082
|
+
}
|
|
23043
23083
|
function getApiKey() {
|
|
23044
23084
|
const config2 = loadConfig();
|
|
23045
23085
|
if (!config2.apiKey) {
|
|
@@ -23052,16 +23092,39 @@ function getApiUrl() {
|
|
|
23052
23092
|
const config2 = loadConfig();
|
|
23053
23093
|
return config2.apiUrl;
|
|
23054
23094
|
}
|
|
23055
|
-
function
|
|
23056
|
-
|
|
23095
|
+
function getUserEmail() {
|
|
23096
|
+
const config2 = loadConfig();
|
|
23097
|
+
return config2.userEmail;
|
|
23098
|
+
}
|
|
23099
|
+
function setUserEmail(email2) {
|
|
23100
|
+
saveConfig({ userEmail: email2 });
|
|
23057
23101
|
}
|
|
23058
|
-
function
|
|
23059
|
-
|
|
23102
|
+
function setActiveWorkspace(workspaceId, options) {
|
|
23103
|
+
if (options?.local) {
|
|
23104
|
+
saveLocalConfig({ workspaceId }, options.cwd);
|
|
23105
|
+
} else {
|
|
23106
|
+
saveConfig({ activeWorkspaceId: workspaceId });
|
|
23107
|
+
}
|
|
23060
23108
|
}
|
|
23061
|
-
function
|
|
23109
|
+
function setActiveProject(projectId, options) {
|
|
23110
|
+
if (options?.local) {
|
|
23111
|
+
saveLocalConfig({ projectId }, options.cwd);
|
|
23112
|
+
} else {
|
|
23113
|
+
saveConfig({ activeProjectId: projectId });
|
|
23114
|
+
}
|
|
23115
|
+
}
|
|
23116
|
+
function getActiveWorkspaceId(cwd) {
|
|
23117
|
+
const localConfig = loadLocalConfig(cwd);
|
|
23118
|
+
if (localConfig?.workspaceId) {
|
|
23119
|
+
return localConfig.workspaceId;
|
|
23120
|
+
}
|
|
23062
23121
|
return loadConfig().activeWorkspaceId;
|
|
23063
23122
|
}
|
|
23064
|
-
function getActiveProjectId() {
|
|
23123
|
+
function getActiveProjectId(cwd) {
|
|
23124
|
+
const localConfig = loadLocalConfig(cwd);
|
|
23125
|
+
if (localConfig?.projectId) {
|
|
23126
|
+
return localConfig.projectId;
|
|
23127
|
+
}
|
|
23065
23128
|
return loadConfig().activeProjectId;
|
|
23066
23129
|
}
|
|
23067
23130
|
function isConfigured() {
|
|
@@ -24230,6 +24293,21 @@ Include: cards moved recently, current in-progress items, blocked or high-priori
|
|
|
24230
24293
|
const cardId = exports_external.string().uuid().parse(args.cardId);
|
|
24231
24294
|
const agentIdentifier = exports_external.string().min(1).parse(args.agentIdentifier);
|
|
24232
24295
|
const agentName = exports_external.string().min(1).parse(args.agentName);
|
|
24296
|
+
let assignedTo = null;
|
|
24297
|
+
const userEmail = getUserEmail();
|
|
24298
|
+
if (userEmail) {
|
|
24299
|
+
try {
|
|
24300
|
+
const workspaceId = getActiveWorkspaceId();
|
|
24301
|
+
if (workspaceId) {
|
|
24302
|
+
const { members } = await client2.getWorkspaceMembers(workspaceId);
|
|
24303
|
+
const user = members.find((m) => m.email === userEmail);
|
|
24304
|
+
if (user) {
|
|
24305
|
+
await client2.updateCard(cardId, { assigneeId: user.id });
|
|
24306
|
+
assignedTo = user.email;
|
|
24307
|
+
}
|
|
24308
|
+
}
|
|
24309
|
+
} catch {}
|
|
24310
|
+
}
|
|
24233
24311
|
const result = await client2.startAgentSession(cardId, {
|
|
24234
24312
|
agentIdentifier,
|
|
24235
24313
|
agentName,
|
|
@@ -24237,7 +24315,7 @@ Include: cards moved recently, current in-progress items, blocked or high-priori
|
|
|
24237
24315
|
currentTask: args.currentTask,
|
|
24238
24316
|
estimatedMinutesRemaining: args.estimatedMinutesRemaining
|
|
24239
24317
|
});
|
|
24240
|
-
return { success: true, ...result };
|
|
24318
|
+
return { success: true, assignedTo, ...result };
|
|
24241
24319
|
}
|
|
24242
24320
|
case "harmony_update_agent_progress": {
|
|
24243
24321
|
const cardId = exports_external.string().uuid().parse(args.cardId);
|