@gethmy/mcp 2.3.1 → 2.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.
Files changed (34) hide show
  1. package/dist/lib/active-learning.js +939 -787
  2. package/dist/lib/api-client.js +2527 -644
  3. package/dist/lib/auto-session.js +177 -196
  4. package/dist/lib/cli.js +34954 -128
  5. package/dist/lib/config.js +235 -201
  6. package/dist/lib/consolidation.js +374 -289
  7. package/dist/lib/context-assembly.js +1265 -838
  8. package/dist/lib/graph-expansion.js +139 -155
  9. package/dist/lib/http.js +1917 -130
  10. package/dist/lib/index.js +29525 -5
  11. package/dist/lib/lifecycle-maintenance.js +663 -79
  12. package/dist/lib/memory-cleanup.js +1315 -409
  13. package/dist/lib/onboard.js +2588 -32
  14. package/dist/lib/prompt-builder.js +438 -445
  15. package/dist/lib/remote.js +31733 -143
  16. package/dist/lib/server.js +29388 -3229
  17. package/dist/lib/skills.js +315 -132
  18. package/dist/lib/tui/agents.js +128 -107
  19. package/dist/lib/tui/docs.js +1590 -687
  20. package/dist/lib/tui/setup.js +5698 -804
  21. package/dist/lib/tui/theme.js +183 -86
  22. package/dist/lib/tui/writer.js +1149 -176
  23. package/package.json +2 -2
  24. package/src/memory-cleanup.ts +2 -4
  25. package/dist/lib/__tests__/active-learning.test.js +0 -386
  26. package/dist/lib/__tests__/agent-performance-profiles.test.js +0 -325
  27. package/dist/lib/__tests__/auto-session.test.js +0 -661
  28. package/dist/lib/__tests__/context-assembly.test.js +0 -362
  29. package/dist/lib/__tests__/graph-expansion.test.js +0 -150
  30. package/dist/lib/__tests__/integration-memory-crud.test.js +0 -797
  31. package/dist/lib/__tests__/integration-memory-system.test.js +0 -281
  32. package/dist/lib/__tests__/lifecycle-maintenance.test.js +0 -207
  33. package/dist/lib/__tests__/pattern-detection.test.js +0 -295
  34. package/dist/lib/__tests__/prompt-builder.test.js +0 -418
@@ -1,13 +1,227 @@
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 __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
29
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
30
+
31
+ // src/config.ts
1
32
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
33
+ import { homedir } from "node:os";
34
+ import { join } from "node:path";
35
+ var DEFAULT_API_URL = "https://app.gethmy.com/api";
36
+ var LOCAL_CONFIG_FILENAME = ".harmony-mcp.json";
37
+ function getConfigDir() {
38
+ return join(homedir(), ".harmony-mcp");
39
+ }
40
+ function getConfigPath() {
41
+ return join(getConfigDir(), "config.json");
42
+ }
43
+ function getLocalConfigPath(cwd) {
44
+ return join(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
45
+ }
46
+ function loadConfig() {
47
+ const configPath = getConfigPath();
48
+ if (!existsSync(configPath)) {
49
+ return {
50
+ apiKey: null,
51
+ apiUrl: DEFAULT_API_URL,
52
+ activeWorkspaceId: null,
53
+ activeProjectId: null,
54
+ userEmail: null,
55
+ memoryDir: null
56
+ };
57
+ }
58
+ try {
59
+ const data = readFileSync(configPath, "utf-8");
60
+ const config = JSON.parse(data);
61
+ return {
62
+ apiKey: config.apiKey || null,
63
+ apiUrl: config.apiUrl || DEFAULT_API_URL,
64
+ activeWorkspaceId: config.activeWorkspaceId || null,
65
+ activeProjectId: config.activeProjectId || null,
66
+ userEmail: config.userEmail || null,
67
+ memoryDir: config.memoryDir || null
68
+ };
69
+ } catch {
70
+ return {
71
+ apiKey: null,
72
+ apiUrl: DEFAULT_API_URL,
73
+ activeWorkspaceId: null,
74
+ activeProjectId: null,
75
+ userEmail: null,
76
+ memoryDir: null
77
+ };
78
+ }
79
+ }
80
+ function saveConfig(config) {
81
+ const configDir = getConfigDir();
82
+ const configPath = getConfigPath();
83
+ if (!existsSync(configDir)) {
84
+ mkdirSync(configDir, { recursive: true, mode: 448 });
85
+ }
86
+ const existingConfig = loadConfig();
87
+ const newConfig = { ...existingConfig, ...config };
88
+ writeFileSync(configPath, JSON.stringify(newConfig, null, 2), {
89
+ mode: 384
90
+ });
91
+ }
92
+ function loadLocalConfig(cwd) {
93
+ const localConfigPath = getLocalConfigPath(cwd);
94
+ if (!existsSync(localConfigPath)) {
95
+ return null;
96
+ }
97
+ try {
98
+ const data = readFileSync(localConfigPath, "utf-8");
99
+ const config = JSON.parse(data);
100
+ return {
101
+ workspaceId: config.workspaceId || null,
102
+ projectId: config.projectId || null
103
+ };
104
+ } catch {
105
+ return null;
106
+ }
107
+ }
108
+ function saveLocalConfig(config, cwd) {
109
+ const localConfigPath = getLocalConfigPath(cwd);
110
+ const existingConfig = loadLocalConfig(cwd) || {
111
+ workspaceId: null,
112
+ projectId: null
113
+ };
114
+ const newConfig = { ...existingConfig, ...config };
115
+ const cleanConfig = {};
116
+ if (newConfig.workspaceId)
117
+ cleanConfig.workspaceId = newConfig.workspaceId;
118
+ if (newConfig.projectId)
119
+ cleanConfig.projectId = newConfig.projectId;
120
+ writeFileSync(localConfigPath, JSON.stringify(cleanConfig, null, 2));
121
+ }
122
+ function hasLocalConfig(cwd) {
123
+ return existsSync(getLocalConfigPath(cwd));
124
+ }
125
+ function getApiKey() {
126
+ const config = loadConfig();
127
+ if (!config.apiKey) {
128
+ throw new Error(`Not configured. Run "npx @gethmy/mcp setup" to set your API key.
129
+ ` + "You can generate an API key at https://gethmy.com → Settings → API Keys.");
130
+ }
131
+ return config.apiKey;
132
+ }
133
+ function getApiUrl() {
134
+ const config = loadConfig();
135
+ return config.apiUrl;
136
+ }
137
+ function getUserEmail() {
138
+ const config = loadConfig();
139
+ return config.userEmail;
140
+ }
141
+ function setUserEmail(email) {
142
+ saveConfig({ userEmail: email });
143
+ }
144
+ function setActiveWorkspace(workspaceId, options) {
145
+ if (options?.local) {
146
+ saveLocalConfig({ workspaceId }, options.cwd);
147
+ } else {
148
+ saveConfig({ activeWorkspaceId: workspaceId });
149
+ }
150
+ }
151
+ function setActiveProject(projectId, options) {
152
+ if (options?.local) {
153
+ saveLocalConfig({ projectId }, options.cwd);
154
+ } else {
155
+ saveConfig({ activeProjectId: projectId });
156
+ }
157
+ }
158
+ function getActiveWorkspaceId(cwd) {
159
+ const localConfig = loadLocalConfig(cwd);
160
+ if (localConfig?.workspaceId) {
161
+ return localConfig.workspaceId;
162
+ }
163
+ return loadConfig().activeWorkspaceId;
164
+ }
165
+ function getActiveProjectId(cwd) {
166
+ const localConfig = loadLocalConfig(cwd);
167
+ if (localConfig?.projectId) {
168
+ return localConfig.projectId;
169
+ }
170
+ return loadConfig().activeProjectId;
171
+ }
172
+ function isConfigured() {
173
+ const config = loadConfig();
174
+ return !!config.apiKey;
175
+ }
176
+ function areSkillsInstalled(cwd) {
177
+ const home = homedir();
178
+ const workingDir = cwd || process.cwd();
179
+ const foundPaths = [];
180
+ const globalSkillsDir = join(home, ".agents", "skills");
181
+ const globalSkillPath = join(globalSkillsDir, "hmy", "SKILL.md");
182
+ if (existsSync(globalSkillPath)) {
183
+ foundPaths.push(globalSkillPath);
184
+ return { installed: true, location: "global", paths: foundPaths };
185
+ }
186
+ const claudeGlobalSkill = join(home, ".claude", "skills", "hmy.md");
187
+ if (existsSync(claudeGlobalSkill)) {
188
+ foundPaths.push(claudeGlobalSkill);
189
+ return { installed: true, location: "global", paths: foundPaths };
190
+ }
191
+ const claudeGlobalSkillAlt = join(home, ".claude", "skills", "hmy", "SKILL.md");
192
+ if (existsSync(claudeGlobalSkillAlt)) {
193
+ foundPaths.push(claudeGlobalSkillAlt);
194
+ return { installed: true, location: "global", paths: foundPaths };
195
+ }
196
+ const localSkillPath = join(workingDir, ".claude", "skills", "hmy.md");
197
+ if (existsSync(localSkillPath)) {
198
+ foundPaths.push(localSkillPath);
199
+ return { installed: true, location: "local", paths: foundPaths };
200
+ }
201
+ const localSkillPathAlt = join(workingDir, ".claude", "skills", "hmy", "SKILL.md");
202
+ if (existsSync(localSkillPathAlt)) {
203
+ foundPaths.push(localSkillPathAlt);
204
+ return { installed: true, location: "local", paths: foundPaths };
205
+ }
206
+ return { installed: false, location: null, paths: [] };
207
+ }
208
+ function hasProjectContext(cwd) {
209
+ const localConfig = loadLocalConfig(cwd);
210
+ return !!(localConfig?.workspaceId || localConfig?.projectId);
211
+ }
212
+ function getMemoryDir() {
213
+ const config = loadConfig();
214
+ if (config.memoryDir)
215
+ return config.memoryDir;
216
+ return join(homedir(), ".harmony", "memory");
217
+ }
218
+
219
+ // src/skills.ts
220
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
2
221
  import { dirname } from "node:path";
3
- import { areSkillsInstalled } from "./config.js";
4
- export const SKILLS_VERSION = "4";
5
- const VERSION_MARKER_PREFIX = "<!-- skills-version:";
6
- /**
7
- * Legacy workflow prompt used by Codex, Cursor agents.
8
- * Claude Code skills use the newer SKILL_DEFINITIONS content instead.
9
- */
10
- export const HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
222
+ var SKILLS_VERSION = "4";
223
+ var VERSION_MARKER_PREFIX = "<!-- skills-version:";
224
+ var HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
11
225
 
12
226
  Start work on a Harmony card. Card reference: $ARGUMENTS
13
227
 
@@ -82,10 +296,7 @@ If pausing: \`harmony_end_agent_session\` with \`status: "paused"\`
82
296
 
83
297
  **AI:** \`harmony_generate_prompt\`, \`harmony_process_command\`
84
298
  `;
85
- /**
86
- * New Claude Code skill content with intent detection.
87
- */
88
- const HMY_SKILL_CONTENT = `# Harmony Card Workflow
299
+ var HMY_SKILL_CONTENT = `# Harmony Card Workflow
89
300
 
90
301
  User input: $ARGUMENTS
91
302
 
@@ -242,7 +453,7 @@ Proceed normally without a session. No action needed.
242
453
 
243
454
  **AI:** \`harmony_generate_prompt\`, \`harmony_process_command\`
244
455
  `;
245
- const HMY_PLAN_CONTENT = `# Harmony Plan Workflow
456
+ var HMY_PLAN_CONTENT = `# Harmony Plan Workflow
246
457
 
247
458
  Create a new plan or work on an existing one. Argument: $ARGUMENTS
248
459
 
@@ -455,139 +666,111 @@ Report the result:
455
666
  **Planning:** \`harmony_create_plan\`, \`harmony_advance_plan\`, \`harmony_update_plan\`
456
667
  **Execution:** \`harmony_create_card\`, \`harmony_update_card\`
457
668
  `;
458
- export const SKILL_DEFINITIONS = {
459
- hmy: {
460
- name: "hmy",
461
- description: "Work with Harmony cards — create, view, update, or start working on them. Use when given a card reference like #42, or commands like create, move, update.",
462
- argumentHint: "<command or card-reference>",
463
- content: HMY_SKILL_CONTENT,
464
- },
465
- "hmy-plan": {
466
- name: "hmy-plan",
467
- description: "Create a new plan or work on an existing one. Use when asked to plan a feature, execute a plan, review a plan, or given a plan reference.",
468
- argumentHint: "[plan name, ID, or topic to plan]",
469
- content: HMY_PLAN_CONTENT,
470
- },
669
+ var SKILL_DEFINITIONS = {
670
+ hmy: {
671
+ name: "hmy",
672
+ description: "Work with Harmony cards — create, view, update, or start working on them. Use when given a card reference like #42, or commands like create, move, update.",
673
+ argumentHint: "<command or card-reference>",
674
+ content: HMY_SKILL_CONTENT
675
+ },
676
+ "hmy-plan": {
677
+ name: "hmy-plan",
678
+ description: "Create a new plan or work on an existing one. Use when asked to plan a feature, execute a plan, review a plan, or given a plan reference.",
679
+ argumentHint: "[plan name, ID, or topic to plan]",
680
+ content: HMY_PLAN_CONTENT
681
+ }
471
682
  };
472
- /**
473
- * Build a complete skill file with frontmatter and version marker.
474
- * Optionally substitutes agent identifier/name for agent-specific builds.
475
- */
476
- export function buildSkillFile(skillId, agentId) {
477
- const skill = SKILL_DEFINITIONS[skillId];
478
- if (!skill) {
479
- throw new Error(`Unknown skill: ${skillId}`);
480
- }
481
- let content = skill.content;
482
- // Apply agent-specific substitutions
483
- if (agentId === "claude") {
484
- content = content
485
- .replace("Your agent identifier", "claude-code")
486
- .replace("Your agent name", "Claude Code");
487
- }
488
- const frontmatter = `---
683
+ function buildSkillFile(skillId, agentId) {
684
+ const skill = SKILL_DEFINITIONS[skillId];
685
+ if (!skill) {
686
+ throw new Error(`Unknown skill: ${skillId}`);
687
+ }
688
+ let content = skill.content;
689
+ if (agentId === "claude") {
690
+ content = content.replace("Your agent identifier", "claude-code").replace("Your agent name", "Claude Code");
691
+ }
692
+ const frontmatter = `---
489
693
  name: ${skill.name}
490
694
  description: ${skill.description}
491
695
  argument-hint: ${skill.argumentHint}
492
696
  ---`;
493
- return `${frontmatter}\n\n${content}\n${VERSION_MARKER_PREFIX}${SKILLS_VERSION} -->`;
697
+ return `${frontmatter}
698
+
699
+ ${content}
700
+ ${VERSION_MARKER_PREFIX}${SKILLS_VERSION} -->`;
494
701
  }
495
- /**
496
- * Parse the skills version from a skill file's content.
497
- * Returns null if no version marker is found.
498
- */
499
702
  function parseSkillVersion(content) {
500
- const match = content.match(/<!-- skills-version:(\d+) -->/);
501
- return match ? match[1] : null;
703
+ const match = content.match(/<!-- skills-version:(\d+) -->/);
704
+ return match ? match[1] : null;
502
705
  }
503
- /**
504
- * Find all skill files in a given directory (global or local).
505
- * Returns an array of { skillId, filePath } for each found skill.
506
- */
507
706
  function findSkillFiles(paths) {
508
- const results = [];
509
- for (const filePath of paths) {
510
- if (!existsSync(filePath))
511
- continue;
512
- // Determine skill ID from path
513
- for (const skillId of Object.keys(SKILL_DEFINITIONS)) {
514
- if (filePath.includes(`/${skillId}/`) ||
515
- filePath.includes(`/${skillId}.md`)) {
516
- results.push({ skillId, filePath });
517
- break;
518
- }
519
- }
707
+ const results = [];
708
+ for (const filePath of paths) {
709
+ if (!existsSync2(filePath))
710
+ continue;
711
+ for (const skillId of Object.keys(SKILL_DEFINITIONS)) {
712
+ if (filePath.includes(`/${skillId}/`) || filePath.includes(`/${skillId}.md`)) {
713
+ results.push({ skillId, filePath });
714
+ break;
715
+ }
520
716
  }
521
- return results;
717
+ }
718
+ return results;
522
719
  }
523
- /**
524
- * Silently refresh installed skill files if they are outdated.
525
- * Called at MCP server startup. Non-blocking — errors are caught and logged.
526
- */
527
- export async function refreshSkills() {
528
- try {
529
- const status = areSkillsInstalled();
530
- if (!status.installed) {
531
- // User hasn't run setup yet — don't install skills
532
- return;
533
- }
534
- // Find skill files from the detected paths
535
- const skillFiles = findSkillFiles(status.paths);
536
- // Also check for sibling skills in the same directory
537
- // e.g., if hmy is installed at ~/.agents/skills/hmy/SKILL.md,
538
- // check for hmy-plan at ~/.agents/skills/hmy-plan/SKILL.md
539
- if (skillFiles.length > 0) {
540
- const samplePath = skillFiles[0].filePath;
541
- for (const skillId of Object.keys(SKILL_DEFINITIONS)) {
542
- const alreadyFound = skillFiles.some((sf) => sf.skillId === skillId);
543
- if (alreadyFound)
544
- continue;
545
- // Try to find sibling skill file
546
- let siblingPath;
547
- if (samplePath.endsWith("SKILL.md")) {
548
- // ~/.agents/skills/hmy/SKILL.md -> ~/.agents/skills/hmy-plan/SKILL.md
549
- const parentDir = dirname(dirname(samplePath));
550
- siblingPath = `${parentDir}/${skillId}/SKILL.md`;
551
- }
552
- else {
553
- // ~/.claude/skills/hmy.md -> ~/.claude/skills/hmy-plan.md
554
- const parentDir = dirname(samplePath);
555
- siblingPath = `${parentDir}/${skillId}.md`;
556
- }
557
- if (existsSync(siblingPath)) {
558
- skillFiles.push({ skillId, filePath: siblingPath });
559
- }
560
- }
561
- }
562
- if (skillFiles.length === 0) {
563
- return;
720
+ async function refreshSkills() {
721
+ try {
722
+ const status = areSkillsInstalled();
723
+ if (!status.installed) {
724
+ return;
725
+ }
726
+ const skillFiles = findSkillFiles(status.paths);
727
+ if (skillFiles.length > 0) {
728
+ const samplePath = skillFiles[0].filePath;
729
+ for (const skillId of Object.keys(SKILL_DEFINITIONS)) {
730
+ const alreadyFound = skillFiles.some((sf) => sf.skillId === skillId);
731
+ if (alreadyFound)
732
+ continue;
733
+ let siblingPath;
734
+ if (samplePath.endsWith("SKILL.md")) {
735
+ const parentDir = dirname(dirname(samplePath));
736
+ siblingPath = `${parentDir}/${skillId}/SKILL.md`;
737
+ } else {
738
+ const parentDir = dirname(samplePath);
739
+ siblingPath = `${parentDir}/${skillId}.md`;
564
740
  }
565
- let updated = false;
566
- for (const { skillId, filePath } of skillFiles) {
567
- try {
568
- const currentContent = readFileSync(filePath, "utf-8");
569
- const currentVersion = parseSkillVersion(currentContent);
570
- // Update if no version marker or version is older
571
- if (currentVersion === null ||
572
- Number(currentVersion) < Number(SKILLS_VERSION)) {
573
- const newContent = buildSkillFile(skillId, "claude");
574
- const dir = dirname(filePath);
575
- if (!existsSync(dir)) {
576
- mkdirSync(dir, { recursive: true });
577
- }
578
- writeFileSync(filePath, newContent);
579
- updated = true;
580
- }
581
- }
582
- catch {
583
- // Silently skip files we can't read/write
584
- }
741
+ if (existsSync2(siblingPath)) {
742
+ skillFiles.push({ skillId, filePath: siblingPath });
585
743
  }
586
- if (updated) {
587
- console.error(`Harmony: Updated skills to v${SKILLS_VERSION}`);
744
+ }
745
+ }
746
+ if (skillFiles.length === 0) {
747
+ return;
748
+ }
749
+ let updated = false;
750
+ for (const { skillId, filePath } of skillFiles) {
751
+ try {
752
+ const currentContent = readFileSync2(filePath, "utf-8");
753
+ const currentVersion = parseSkillVersion(currentContent);
754
+ if (currentVersion === null || Number(currentVersion) < Number(SKILLS_VERSION)) {
755
+ const newContent = buildSkillFile(skillId, "claude");
756
+ const dir = dirname(filePath);
757
+ if (!existsSync2(dir)) {
758
+ mkdirSync2(dir, { recursive: true });
759
+ }
760
+ writeFileSync2(filePath, newContent);
761
+ updated = true;
588
762
  }
763
+ } catch {}
589
764
  }
590
- catch {
591
- // Non-blocking if anything fails, continue silently
765
+ if (updated) {
766
+ console.error(`Harmony: Updated skills to v${SKILLS_VERSION}`);
592
767
  }
768
+ } catch {}
593
769
  }
770
+ export {
771
+ refreshSkills,
772
+ buildSkillFile,
773
+ SKILL_DEFINITIONS,
774
+ SKILLS_VERSION,
775
+ HARMONY_WORKFLOW_PROMPT
776
+ };