@gethmy/mcp 1.0.0 → 2.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 +201 -36
- package/dist/cli.js +20938 -20249
- package/dist/http.js +1957 -0
- package/dist/index.js +17833 -17888
- package/dist/lib/__tests__/active-learning.test.js +386 -0
- package/dist/lib/__tests__/agent-performance-profiles.test.js +325 -0
- package/dist/lib/__tests__/auto-session.test.js +661 -0
- package/dist/lib/__tests__/context-assembly.test.js +362 -0
- package/dist/lib/__tests__/graph-expansion.test.js +150 -0
- package/dist/lib/__tests__/integration-memory-crud.test.js +797 -0
- package/dist/lib/__tests__/integration-memory-system.test.js +281 -0
- package/dist/lib/__tests__/lifecycle-maintenance.test.js +207 -0
- package/dist/lib/__tests__/pattern-detection.test.js +295 -0
- package/dist/lib/__tests__/prompt-builder.test.js +418 -0
- package/dist/lib/active-learning.js +878 -0
- package/dist/lib/api-client.js +548 -0
- package/dist/lib/auto-session.js +173 -0
- package/dist/lib/cli.js +127 -0
- package/dist/lib/config.js +205 -0
- package/dist/lib/consolidation.js +243 -0
- package/dist/lib/context-assembly.js +606 -0
- package/dist/lib/graph-expansion.js +163 -0
- package/dist/lib/http.js +174 -0
- package/dist/lib/index.js +7 -0
- package/dist/lib/lifecycle-maintenance.js +88 -0
- package/dist/lib/prompt-builder.js +483 -0
- package/dist/lib/remote.js +166 -0
- package/dist/lib/server.js +3132 -0
- package/dist/lib/tui/agents.js +116 -0
- package/dist/lib/tui/docs.js +558 -0
- package/dist/lib/tui/setup.js +1068 -0
- package/dist/lib/tui/theme.js +95 -0
- package/dist/lib/tui/writer.js +200 -0
- package/dist/remote.js +34534 -0
- package/dist/server.js +31967 -0
- package/package.json +20 -7
- package/src/__tests__/active-learning.test.ts +483 -0
- package/src/__tests__/agent-performance-profiles.test.ts +468 -0
- package/src/__tests__/auto-session.test.ts +912 -0
- package/src/__tests__/context-assembly.test.ts +506 -0
- package/src/__tests__/graph-expansion.test.ts +285 -0
- package/src/__tests__/integration-memory-crud.test.ts +948 -0
- package/src/__tests__/integration-memory-system.test.ts +321 -0
- package/src/__tests__/lifecycle-maintenance.test.ts +238 -0
- package/src/__tests__/pattern-detection.test.ts +438 -0
- package/src/__tests__/prompt-builder.test.ts +505 -0
- package/src/active-learning.ts +1227 -0
- package/src/api-client.ts +963 -0
- package/src/auto-session.ts +218 -0
- package/src/cli.ts +166 -0
- package/src/config.ts +285 -0
- package/src/consolidation.ts +314 -0
- package/src/context-assembly.ts +842 -0
- package/src/graph-expansion.ts +234 -0
- package/src/http.ts +265 -0
- package/src/index.ts +8 -0
- package/src/lifecycle-maintenance.ts +120 -0
- package/src/prompt-builder.ts +681 -0
- package/src/remote.ts +227 -0
- package/src/server.ts +3858 -0
- package/src/tui/agents.ts +154 -0
- package/src/tui/docs.ts +650 -0
- package/src/tui/setup.ts +1281 -0
- package/src/tui/theme.ts +114 -0
- package/src/tui/writer.ts +260 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import * as pc from "picocolors";
|
|
2
|
+
/**
|
|
3
|
+
* Consistent theme for Harmony MCP TUI
|
|
4
|
+
* Uses picocolors for lightweight color output
|
|
5
|
+
*/
|
|
6
|
+
// Brand colors and symbols
|
|
7
|
+
export const symbols = {
|
|
8
|
+
harmony: "\u25B2", // Triangle for Harmony logo
|
|
9
|
+
check: "\u2713",
|
|
10
|
+
cross: "\u2717",
|
|
11
|
+
bullet: "\u2022",
|
|
12
|
+
arrow: "\u2192",
|
|
13
|
+
arrowRight: "\u25B8",
|
|
14
|
+
dot: "\u25CF",
|
|
15
|
+
dotEmpty: "\u25CB",
|
|
16
|
+
info: "\u2139",
|
|
17
|
+
warning: "\u26A0",
|
|
18
|
+
pointer: "\u276F",
|
|
19
|
+
};
|
|
20
|
+
// Color functions
|
|
21
|
+
export const colors = {
|
|
22
|
+
// Brand
|
|
23
|
+
brand: (text) => pc.cyan(text),
|
|
24
|
+
brandBold: (text) => pc.bold(pc.cyan(text)),
|
|
25
|
+
// Status
|
|
26
|
+
success: (text) => pc.green(text),
|
|
27
|
+
error: (text) => pc.red(text),
|
|
28
|
+
warning: (text) => pc.yellow(text),
|
|
29
|
+
info: (text) => pc.blue(text),
|
|
30
|
+
// Text
|
|
31
|
+
dim: (text) => pc.dim(text),
|
|
32
|
+
bold: (text) => pc.bold(text),
|
|
33
|
+
muted: (text) => pc.gray(text),
|
|
34
|
+
// Highlights
|
|
35
|
+
highlight: (text) => pc.cyan(text),
|
|
36
|
+
link: (text) => pc.underline(pc.cyan(text)),
|
|
37
|
+
};
|
|
38
|
+
// Styled messages
|
|
39
|
+
export const messages = {
|
|
40
|
+
header: () => {
|
|
41
|
+
return `
|
|
42
|
+
${colors.brandBold(" HARMONY")}
|
|
43
|
+
${colors.dim(" Project management for AI agents")}
|
|
44
|
+
`;
|
|
45
|
+
},
|
|
46
|
+
done: (message) => {
|
|
47
|
+
return `${colors.success(symbols.check)} ${message}`;
|
|
48
|
+
},
|
|
49
|
+
fail: (message) => {
|
|
50
|
+
return `${colors.error(symbols.cross)} ${message}`;
|
|
51
|
+
},
|
|
52
|
+
skip: (message) => {
|
|
53
|
+
return `${colors.dim("-")} ${colors.dim(message)}`;
|
|
54
|
+
},
|
|
55
|
+
step: (number, total, message) => {
|
|
56
|
+
return `${colors.dim(`[${number}/${total}]`)} ${message}`;
|
|
57
|
+
},
|
|
58
|
+
fileCreated: (path) => {
|
|
59
|
+
return ` ${colors.success(symbols.check)} ${colors.dim(path)}`;
|
|
60
|
+
},
|
|
61
|
+
fileSkipped: (path) => {
|
|
62
|
+
return ` ${colors.dim(symbols.bullet)} ${colors.dim(path)} ${colors.dim("(exists)")}`;
|
|
63
|
+
},
|
|
64
|
+
fileError: (path, error) => {
|
|
65
|
+
return ` ${colors.error(symbols.cross)} ${path}: ${colors.error(error)}`;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
// Box drawing for sections
|
|
69
|
+
export function box(title, content) {
|
|
70
|
+
const lines = content.split("\n");
|
|
71
|
+
const maxWidth = Math.max(title.length, ...lines.map((l) => l.length));
|
|
72
|
+
const width = maxWidth + 4;
|
|
73
|
+
const top = `\u250C${"─".repeat(width)}\u2510`;
|
|
74
|
+
const bottom = `\u2514${"─".repeat(width)}\u2518`;
|
|
75
|
+
const titleLine = `\u2502 ${colors.bold(title)}${" ".repeat(width - title.length - 1)}\u2502`;
|
|
76
|
+
const contentLines = lines
|
|
77
|
+
.map((line) => `\u2502 ${line}${" ".repeat(width - line.length - 1)}\u2502`)
|
|
78
|
+
.join("\n");
|
|
79
|
+
return `${top}\n${titleLine}\n${contentLines}\n${bottom}`;
|
|
80
|
+
}
|
|
81
|
+
// Format file paths for display (shorten home directory)
|
|
82
|
+
export function formatPath(path, homeDir) {
|
|
83
|
+
if (path.startsWith(homeDir)) {
|
|
84
|
+
return `~${path.slice(homeDir.length)}`;
|
|
85
|
+
}
|
|
86
|
+
return path;
|
|
87
|
+
}
|
|
88
|
+
// Indent helper
|
|
89
|
+
export function indent(text, spaces = 2) {
|
|
90
|
+
const pad = " ".repeat(spaces);
|
|
91
|
+
return text
|
|
92
|
+
.split("\n")
|
|
93
|
+
.map((line) => `${pad}${line}`)
|
|
94
|
+
.join("\n");
|
|
95
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import * as p from "@clack/prompts";
|
|
5
|
+
import { colors, formatPath, messages } from "./theme.js";
|
|
6
|
+
/**
|
|
7
|
+
* Ensure directory exists
|
|
8
|
+
*/
|
|
9
|
+
function ensureDir(dirPath) {
|
|
10
|
+
if (!existsSync(dirPath)) {
|
|
11
|
+
mkdirSync(dirPath, { recursive: true, mode: 0o755 });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Write a file, optionally skipping if exists
|
|
16
|
+
*/
|
|
17
|
+
export function writeFile(filePath, content, options = {}) {
|
|
18
|
+
const exists = existsSync(filePath);
|
|
19
|
+
if (exists && !options.force) {
|
|
20
|
+
return { path: filePath, action: "skip" };
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
ensureDir(dirname(filePath));
|
|
24
|
+
// Use restrictive permissions for config files
|
|
25
|
+
const mode = filePath.includes(".harmony-mcp") ? 0o600 : 0o644;
|
|
26
|
+
writeFileSync(filePath, content, { mode });
|
|
27
|
+
return { path: filePath, action: exists ? "update" : "create" };
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return {
|
|
31
|
+
path: filePath,
|
|
32
|
+
action: "skip",
|
|
33
|
+
error: error instanceof Error ? error.message : String(error),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Merge JSON content into existing file
|
|
39
|
+
*/
|
|
40
|
+
export function mergeJsonFile(filePath, updates, options = {}) {
|
|
41
|
+
const exists = existsSync(filePath);
|
|
42
|
+
if (!exists) {
|
|
43
|
+
try {
|
|
44
|
+
ensureDir(dirname(filePath));
|
|
45
|
+
writeFileSync(filePath, JSON.stringify(updates, null, 2), {
|
|
46
|
+
mode: 0o644,
|
|
47
|
+
});
|
|
48
|
+
return { path: filePath, action: "create" };
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
return {
|
|
52
|
+
path: filePath,
|
|
53
|
+
action: "skip",
|
|
54
|
+
error: error instanceof Error ? error.message : String(error),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const existing = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
60
|
+
// Deep merge mcpServers if present (always update harmony config)
|
|
61
|
+
if (updates.mcpServers && existing.mcpServers) {
|
|
62
|
+
const existingServers = existing.mcpServers;
|
|
63
|
+
const updateServers = updates.mcpServers;
|
|
64
|
+
existing.mcpServers = { ...existingServers, ...updateServers };
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
Object.assign(existing, updates);
|
|
68
|
+
}
|
|
69
|
+
writeFileSync(filePath, JSON.stringify(existing, null, 2), { mode: 0o644 });
|
|
70
|
+
return { path: filePath, action: "merge" };
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
if (options.force) {
|
|
74
|
+
try {
|
|
75
|
+
writeFileSync(filePath, JSON.stringify(updates, null, 2), {
|
|
76
|
+
mode: 0o644,
|
|
77
|
+
});
|
|
78
|
+
return { path: filePath, action: "update" };
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
return {
|
|
82
|
+
path: filePath,
|
|
83
|
+
action: "skip",
|
|
84
|
+
error: error instanceof Error ? error.message : String(error),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
path: filePath,
|
|
90
|
+
action: "skip",
|
|
91
|
+
error: "Could not parse existing file",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Append to TOML file (for Codex config)
|
|
97
|
+
*/
|
|
98
|
+
export function appendToToml(filePath, section, content, options = {}) {
|
|
99
|
+
const exists = existsSync(filePath);
|
|
100
|
+
if (!exists) {
|
|
101
|
+
try {
|
|
102
|
+
ensureDir(dirname(filePath));
|
|
103
|
+
writeFileSync(filePath, content, { mode: 0o644 });
|
|
104
|
+
return { path: filePath, action: "create" };
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
return {
|
|
108
|
+
path: filePath,
|
|
109
|
+
action: "skip",
|
|
110
|
+
error: error instanceof Error ? error.message : String(error),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const existing = readFileSync(filePath, "utf-8");
|
|
116
|
+
if (existing.includes(section)) {
|
|
117
|
+
if (options.force) {
|
|
118
|
+
// Replace existing section
|
|
119
|
+
const updated = existing.replace(new RegExp(`\\[${section.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\][\\s\\S]*?(?=\\[|$)`), content.trim() + "\n\n");
|
|
120
|
+
writeFileSync(filePath, updated, { mode: 0o644 });
|
|
121
|
+
return { path: filePath, action: "update" };
|
|
122
|
+
}
|
|
123
|
+
return { path: filePath, action: "skip" };
|
|
124
|
+
}
|
|
125
|
+
// Append new section
|
|
126
|
+
writeFileSync(filePath, existing + "\n" + content, { mode: 0o644 });
|
|
127
|
+
return { path: filePath, action: "merge" };
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return {
|
|
131
|
+
path: filePath,
|
|
132
|
+
action: "skip",
|
|
133
|
+
error: error instanceof Error ? error.message : String(error),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Write multiple files with progress display
|
|
139
|
+
*/
|
|
140
|
+
export async function writeFilesWithProgress(files, options = {}) {
|
|
141
|
+
const results = [];
|
|
142
|
+
const home = homedir();
|
|
143
|
+
const spinner = p.spinner();
|
|
144
|
+
spinner.start("Writing configuration files...");
|
|
145
|
+
for (const file of files) {
|
|
146
|
+
let result;
|
|
147
|
+
if (file.type === "json") {
|
|
148
|
+
const jsonContent = JSON.parse(file.content);
|
|
149
|
+
result = mergeJsonFile(file.path, jsonContent, options);
|
|
150
|
+
}
|
|
151
|
+
else if (file.type === "toml" && file.tomlSection) {
|
|
152
|
+
result = appendToToml(file.path, file.tomlSection, file.content, options);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
result = writeFile(file.path, file.content, options);
|
|
156
|
+
}
|
|
157
|
+
results.push(result);
|
|
158
|
+
// Small delay for visual effect
|
|
159
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
160
|
+
}
|
|
161
|
+
spinner.stop("Files written");
|
|
162
|
+
// Display results
|
|
163
|
+
for (const result of results) {
|
|
164
|
+
const displayPath = formatPath(result.path, home);
|
|
165
|
+
if (result.error) {
|
|
166
|
+
console.log(messages.fileError(displayPath, result.error));
|
|
167
|
+
}
|
|
168
|
+
else if (result.action === "skip") {
|
|
169
|
+
console.log(messages.fileSkipped(displayPath));
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
const actionLabel = result.action === "merge" ? "updated" : "created";
|
|
173
|
+
console.log(` ${colors.success("\u2713")} ${colors.dim(displayPath)} ${colors.dim(`(${actionLabel})`)}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return results;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get summary of what will be written
|
|
180
|
+
*/
|
|
181
|
+
export function getWriteSummary(files, options = {}) {
|
|
182
|
+
const toCreate = [];
|
|
183
|
+
const toUpdate = [];
|
|
184
|
+
const toSkip = [];
|
|
185
|
+
const home = homedir();
|
|
186
|
+
for (const file of files) {
|
|
187
|
+
const displayPath = formatPath(file.path, home);
|
|
188
|
+
const exists = existsSync(file.path);
|
|
189
|
+
if (exists && !options.force) {
|
|
190
|
+
toSkip.push(displayPath);
|
|
191
|
+
}
|
|
192
|
+
else if (exists) {
|
|
193
|
+
toUpdate.push(displayPath);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
toCreate.push(displayPath);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return { toCreate, toUpdate, toSkip };
|
|
200
|
+
}
|