@gethmy/mcp 2.0.0 → 2.1.1
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 +6 -1
- package/dist/cli.js +711 -59
- package/dist/index.js +5 -3
- 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 +550 -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 +744 -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/package.json +15 -6
- 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 +969 -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 +863 -0
- package/src/tui/setup.ts +1281 -0
- package/src/tui/theme.ts +114 -0
- package/src/tui/writer.ts +260 -0
package/dist/cli.js
CHANGED
|
@@ -8828,7 +8828,7 @@ function hasLocalConfig(cwd) {
|
|
|
8828
8828
|
function getApiKey() {
|
|
8829
8829
|
const config = loadConfig();
|
|
8830
8830
|
if (!config.apiKey) {
|
|
8831
|
-
throw new Error(`Not configured. Run "
|
|
8831
|
+
throw new Error(`Not configured. Run "npx @gethmy/mcp setup" to set your API key.
|
|
8832
8832
|
` + "You can generate an API key at https://gethmy.com → Settings → API Keys.");
|
|
8833
8833
|
}
|
|
8834
8834
|
return config.apiKey;
|
|
@@ -26301,6 +26301,8 @@ class HarmonyApiClient {
|
|
|
26301
26301
|
params.set("summary", "true");
|
|
26302
26302
|
if (options?.includeArchived)
|
|
26303
26303
|
params.set("include_archived", "true");
|
|
26304
|
+
if (options?.labelName)
|
|
26305
|
+
params.set("label_name", options.labelName);
|
|
26304
26306
|
const query = params.toString() ? `?${params.toString()}` : "";
|
|
26305
26307
|
return this.request("GET", `/board/${projectId}${query}`);
|
|
26306
26308
|
}
|
|
@@ -29409,7 +29411,7 @@ function registerHandlers(server, deps) {
|
|
|
29409
29411
|
async function handleToolCall(name, args, deps) {
|
|
29410
29412
|
const unauthenticatedTools = ["harmony_signup", "harmony_onboard"];
|
|
29411
29413
|
if (!unauthenticatedTools.includes(name) && !deps.isConfigured()) {
|
|
29412
|
-
throw new Error(`Not configured. Run "
|
|
29414
|
+
throw new Error(`Not configured. Run "npx @gethmy/mcp setup" to set your API key.
|
|
29413
29415
|
` + `You can generate an API key at https://gethmy.com → Settings → API Keys.
|
|
29414
29416
|
` + 'Or use "harmony_onboard" to create an account and configure automatically.');
|
|
29415
29417
|
}
|
|
@@ -30704,7 +30706,7 @@ function createConfigDeps() {
|
|
|
30704
30706
|
class HarmonyMCPServer {
|
|
30705
30707
|
server;
|
|
30706
30708
|
constructor() {
|
|
30707
|
-
this.server = new Server({ name: "
|
|
30709
|
+
this.server = new Server({ name: "@gethmy/mcp", version: "2.0.0" }, { capabilities: { tools: {}, resources: {} } });
|
|
30708
30710
|
registerHandlers(this.server, createConfigDeps());
|
|
30709
30711
|
}
|
|
30710
30712
|
async run() {
|
|
@@ -30745,14 +30747,14 @@ class HarmonyMCPServer {
|
|
|
30745
30747
|
|
|
30746
30748
|
// src/tui/setup.ts
|
|
30747
30749
|
import {
|
|
30748
|
-
existsSync as
|
|
30750
|
+
existsSync as existsSync6,
|
|
30749
30751
|
lstatSync,
|
|
30750
30752
|
mkdirSync as mkdirSync4,
|
|
30751
30753
|
symlinkSync,
|
|
30752
30754
|
unlinkSync
|
|
30753
30755
|
} from "node:fs";
|
|
30754
30756
|
import { homedir as homedir4 } from "node:os";
|
|
30755
|
-
import { dirname as dirname2, join as
|
|
30757
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
30756
30758
|
|
|
30757
30759
|
// ../../node_modules/@clack/core/dist/index.mjs
|
|
30758
30760
|
var import_sisteransi = __toESM(require_src(), 1);
|
|
@@ -31519,6 +31521,10 @@ function detectAgents(cwd = process.cwd()) {
|
|
|
31519
31521
|
});
|
|
31520
31522
|
}
|
|
31521
31523
|
|
|
31524
|
+
// src/tui/docs.ts
|
|
31525
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync } from "node:fs";
|
|
31526
|
+
import { isAbsolute, join as join4, resolve, sep as sep2 } from "node:path";
|
|
31527
|
+
|
|
31522
31528
|
// src/tui/theme.ts
|
|
31523
31529
|
var pc = __toESM(require_picocolors(), 1);
|
|
31524
31530
|
var symbols = {
|
|
@@ -31583,17 +31589,654 @@ function formatPath(path, homeDir) {
|
|
|
31583
31589
|
return path;
|
|
31584
31590
|
}
|
|
31585
31591
|
|
|
31592
|
+
// src/tui/docs.ts
|
|
31593
|
+
var IGNORED_DIRS = new Set([
|
|
31594
|
+
"node_modules",
|
|
31595
|
+
".git",
|
|
31596
|
+
"dist",
|
|
31597
|
+
"build",
|
|
31598
|
+
".next",
|
|
31599
|
+
".nuxt",
|
|
31600
|
+
".output",
|
|
31601
|
+
".vercel",
|
|
31602
|
+
".turbo",
|
|
31603
|
+
".cache",
|
|
31604
|
+
"coverage",
|
|
31605
|
+
".harmony-worktrees",
|
|
31606
|
+
"__pycache__",
|
|
31607
|
+
"target",
|
|
31608
|
+
"vendor"
|
|
31609
|
+
]);
|
|
31610
|
+
function readJson(filePath) {
|
|
31611
|
+
try {
|
|
31612
|
+
return JSON.parse(readFileSync3(filePath, "utf-8"));
|
|
31613
|
+
} catch {
|
|
31614
|
+
return null;
|
|
31615
|
+
}
|
|
31616
|
+
}
|
|
31617
|
+
function readText(filePath) {
|
|
31618
|
+
try {
|
|
31619
|
+
return readFileSync3(filePath, "utf-8");
|
|
31620
|
+
} catch {
|
|
31621
|
+
return null;
|
|
31622
|
+
}
|
|
31623
|
+
}
|
|
31624
|
+
function listDirs(dirPath) {
|
|
31625
|
+
try {
|
|
31626
|
+
return readdirSync2(dirPath).filter((entry) => {
|
|
31627
|
+
if (IGNORED_DIRS.has(entry) || entry.startsWith("."))
|
|
31628
|
+
return false;
|
|
31629
|
+
try {
|
|
31630
|
+
return statSync(join4(dirPath, entry)).isDirectory();
|
|
31631
|
+
} catch {
|
|
31632
|
+
return false;
|
|
31633
|
+
}
|
|
31634
|
+
});
|
|
31635
|
+
} catch {
|
|
31636
|
+
return [];
|
|
31637
|
+
}
|
|
31638
|
+
}
|
|
31639
|
+
var DIR_DESCRIPTIONS = {
|
|
31640
|
+
components: "UI components",
|
|
31641
|
+
pages: "Route-level pages",
|
|
31642
|
+
routes: "Route-level pages",
|
|
31643
|
+
views: "Route-level pages",
|
|
31644
|
+
hooks: "Custom hooks",
|
|
31645
|
+
lib: "Utilities",
|
|
31646
|
+
utils: "Utilities",
|
|
31647
|
+
api: "API / server code",
|
|
31648
|
+
server: "API / server code",
|
|
31649
|
+
contexts: "State management",
|
|
31650
|
+
store: "State management",
|
|
31651
|
+
stores: "State management",
|
|
31652
|
+
types: "Type definitions",
|
|
31653
|
+
styles: "Stylesheets",
|
|
31654
|
+
public: "Static assets",
|
|
31655
|
+
static: "Static assets",
|
|
31656
|
+
assets: "Static assets",
|
|
31657
|
+
supabase: "Supabase backend",
|
|
31658
|
+
functions: "Edge functions",
|
|
31659
|
+
packages: "Monorepo packages",
|
|
31660
|
+
apps: "Monorepo applications",
|
|
31661
|
+
src: "Source code",
|
|
31662
|
+
test: "Tests",
|
|
31663
|
+
tests: "Tests",
|
|
31664
|
+
__tests__: "Tests",
|
|
31665
|
+
scripts: "Build / utility scripts",
|
|
31666
|
+
config: "Configuration",
|
|
31667
|
+
docs: "Documentation",
|
|
31668
|
+
migrations: "Database migrations",
|
|
31669
|
+
prisma: "Prisma schema & migrations",
|
|
31670
|
+
e2e: "End-to-end tests",
|
|
31671
|
+
cypress: "Cypress tests"
|
|
31672
|
+
};
|
|
31673
|
+
function describeDir(name) {
|
|
31674
|
+
return DIR_DESCRIPTIONS[name.toLowerCase()] ?? name;
|
|
31675
|
+
}
|
|
31676
|
+
function scanProject(cwd) {
|
|
31677
|
+
let packageManager = null;
|
|
31678
|
+
if (existsSync4(join4(cwd, "bun.lock")) || existsSync4(join4(cwd, "bun.lockb"))) {
|
|
31679
|
+
packageManager = "bun";
|
|
31680
|
+
} else if (existsSync4(join4(cwd, "pnpm-lock.yaml"))) {
|
|
31681
|
+
packageManager = "pnpm";
|
|
31682
|
+
} else if (existsSync4(join4(cwd, "yarn.lock"))) {
|
|
31683
|
+
packageManager = "yarn";
|
|
31684
|
+
} else if (existsSync4(join4(cwd, "package.json"))) {
|
|
31685
|
+
packageManager = "npm";
|
|
31686
|
+
}
|
|
31687
|
+
const pkg = readJson(join4(cwd, "package.json"));
|
|
31688
|
+
const scripts = pkg && typeof pkg.scripts === "object" && pkg.scripts !== null ? pkg.scripts : {};
|
|
31689
|
+
let language = "unknown";
|
|
31690
|
+
if (existsSync4(join4(cwd, "tsconfig.json"))) {
|
|
31691
|
+
language = "typescript";
|
|
31692
|
+
} else if (existsSync4(join4(cwd, "go.mod"))) {
|
|
31693
|
+
language = "go";
|
|
31694
|
+
} else if (existsSync4(join4(cwd, "Cargo.toml"))) {
|
|
31695
|
+
language = "rust";
|
|
31696
|
+
} else if (existsSync4(join4(cwd, "setup.py")) || existsSync4(join4(cwd, "pyproject.toml"))) {
|
|
31697
|
+
language = "python";
|
|
31698
|
+
} else if (pkg) {
|
|
31699
|
+
language = "javascript";
|
|
31700
|
+
}
|
|
31701
|
+
let framework = null;
|
|
31702
|
+
if (pkg) {
|
|
31703
|
+
const deps = {
|
|
31704
|
+
...typeof pkg.dependencies === "object" ? pkg.dependencies : {},
|
|
31705
|
+
...typeof pkg.devDependencies === "object" ? pkg.devDependencies : {}
|
|
31706
|
+
};
|
|
31707
|
+
if (deps.next) {
|
|
31708
|
+
framework = "next";
|
|
31709
|
+
} else if (deps.react && deps.vite) {
|
|
31710
|
+
framework = "react+vite";
|
|
31711
|
+
} else if (deps.react) {
|
|
31712
|
+
framework = "react";
|
|
31713
|
+
} else if (deps.vue && deps.vite) {
|
|
31714
|
+
framework = "vue+vite";
|
|
31715
|
+
} else if (deps.vue && deps.nuxt) {
|
|
31716
|
+
framework = "nuxt";
|
|
31717
|
+
} else if (deps.vue) {
|
|
31718
|
+
framework = "vue";
|
|
31719
|
+
} else if (deps.astro) {
|
|
31720
|
+
framework = "astro";
|
|
31721
|
+
} else if (deps.svelte) {
|
|
31722
|
+
framework = "svelte";
|
|
31723
|
+
} else if (deps.express) {
|
|
31724
|
+
framework = "express";
|
|
31725
|
+
} else if (deps.fastify) {
|
|
31726
|
+
framework = "fastify";
|
|
31727
|
+
} else if (deps.hono) {
|
|
31728
|
+
framework = "hono";
|
|
31729
|
+
}
|
|
31730
|
+
}
|
|
31731
|
+
let linter = null;
|
|
31732
|
+
if (existsSync4(join4(cwd, "biome.json")) || existsSync4(join4(cwd, "biome.jsonc"))) {
|
|
31733
|
+
linter = "biome";
|
|
31734
|
+
} else {
|
|
31735
|
+
const eslintFiles = [
|
|
31736
|
+
".eslintrc",
|
|
31737
|
+
".eslintrc.js",
|
|
31738
|
+
".eslintrc.cjs",
|
|
31739
|
+
".eslintrc.json",
|
|
31740
|
+
".eslintrc.yml",
|
|
31741
|
+
".eslintrc.yaml",
|
|
31742
|
+
"eslint.config.js",
|
|
31743
|
+
"eslint.config.mjs",
|
|
31744
|
+
"eslint.config.cjs",
|
|
31745
|
+
"eslint.config.ts"
|
|
31746
|
+
];
|
|
31747
|
+
if (eslintFiles.some((f) => existsSync4(join4(cwd, f)))) {
|
|
31748
|
+
linter = "eslint";
|
|
31749
|
+
} else {
|
|
31750
|
+
const prettierFiles = [
|
|
31751
|
+
".prettierrc",
|
|
31752
|
+
".prettierrc.js",
|
|
31753
|
+
".prettierrc.json",
|
|
31754
|
+
".prettierrc.yml",
|
|
31755
|
+
".prettierrc.yaml",
|
|
31756
|
+
"prettier.config.js",
|
|
31757
|
+
"prettier.config.mjs"
|
|
31758
|
+
];
|
|
31759
|
+
if (prettierFiles.some((f) => existsSync4(join4(cwd, f)))) {
|
|
31760
|
+
linter = "prettier";
|
|
31761
|
+
}
|
|
31762
|
+
}
|
|
31763
|
+
}
|
|
31764
|
+
let indentStyle = null;
|
|
31765
|
+
const biome = readJson(join4(cwd, "biome.json")) ?? readJson(join4(cwd, "biome.jsonc"));
|
|
31766
|
+
if (biome) {
|
|
31767
|
+
const formatter = biome.formatter;
|
|
31768
|
+
if (formatter) {
|
|
31769
|
+
const type = formatter.indentStyle === "tab" ? "tab" : "space";
|
|
31770
|
+
const width = typeof formatter.indentWidth === "number" ? formatter.indentWidth : 2;
|
|
31771
|
+
indentStyle = { type, width };
|
|
31772
|
+
}
|
|
31773
|
+
}
|
|
31774
|
+
if (!indentStyle) {
|
|
31775
|
+
const editorConfig = readText(join4(cwd, ".editorconfig"));
|
|
31776
|
+
if (editorConfig) {
|
|
31777
|
+
const styleMatch = editorConfig.match(/indent_style\s*=\s*(space|tab)/);
|
|
31778
|
+
const sizeMatch = editorConfig.match(/indent_size\s*=\s*(\d+)/);
|
|
31779
|
+
if (styleMatch) {
|
|
31780
|
+
indentStyle = {
|
|
31781
|
+
type: styleMatch[1],
|
|
31782
|
+
width: sizeMatch ? Number.parseInt(sizeMatch[1], 10) : 2
|
|
31783
|
+
};
|
|
31784
|
+
}
|
|
31785
|
+
}
|
|
31786
|
+
}
|
|
31787
|
+
const dirs = listDirs(cwd);
|
|
31788
|
+
const srcDirs = existsSync4(join4(cwd, "src")) ? listDirs(join4(cwd, "src")) : [];
|
|
31789
|
+
const monorepo = existsSync4(join4(cwd, "packages")) || existsSync4(join4(cwd, "apps"));
|
|
31790
|
+
const existingDocs = {
|
|
31791
|
+
agentsMd: existsSync4(join4(cwd, "AGENTS.md")),
|
|
31792
|
+
claudeMd: existsSync4(join4(cwd, "CLAUDE.md")),
|
|
31793
|
+
docsDir: existsSync4(join4(cwd, "docs")),
|
|
31794
|
+
architectureMd: existsSync4(join4(cwd, "docs", "architecture.md"))
|
|
31795
|
+
};
|
|
31796
|
+
return {
|
|
31797
|
+
packageManager,
|
|
31798
|
+
scripts,
|
|
31799
|
+
language,
|
|
31800
|
+
framework,
|
|
31801
|
+
linter,
|
|
31802
|
+
indentStyle,
|
|
31803
|
+
dirs,
|
|
31804
|
+
srcDirs,
|
|
31805
|
+
monorepo,
|
|
31806
|
+
existingDocs
|
|
31807
|
+
};
|
|
31808
|
+
}
|
|
31809
|
+
function runCmd(pm) {
|
|
31810
|
+
if (pm === "bun")
|
|
31811
|
+
return "bun run";
|
|
31812
|
+
if (pm === "pnpm")
|
|
31813
|
+
return "pnpm run";
|
|
31814
|
+
if (pm === "yarn")
|
|
31815
|
+
return "yarn";
|
|
31816
|
+
return "npm run";
|
|
31817
|
+
}
|
|
31818
|
+
function describeScript(name) {
|
|
31819
|
+
const map3 = {
|
|
31820
|
+
dev: "Dev server",
|
|
31821
|
+
start: "Start server",
|
|
31822
|
+
build: "Production build",
|
|
31823
|
+
lint: "Lint",
|
|
31824
|
+
"lint:fix": "Lint + autofix",
|
|
31825
|
+
format: "Format code",
|
|
31826
|
+
test: "Run tests",
|
|
31827
|
+
"test:watch": "Run tests (watch)",
|
|
31828
|
+
"test:e2e": "End-to-end tests",
|
|
31829
|
+
typecheck: "Type-check",
|
|
31830
|
+
"type-check": "Type-check",
|
|
31831
|
+
preview: "Preview production build",
|
|
31832
|
+
deploy: "Deploy",
|
|
31833
|
+
generate: "Code generation",
|
|
31834
|
+
migrate: "Run migrations",
|
|
31835
|
+
seed: "Seed database",
|
|
31836
|
+
clean: "Clean build artifacts",
|
|
31837
|
+
prepare: "Prepare (husky, etc.)"
|
|
31838
|
+
};
|
|
31839
|
+
return map3[name] ?? "";
|
|
31840
|
+
}
|
|
31841
|
+
function generateAgentsMd(info, _cwd) {
|
|
31842
|
+
const lang = info.language === "typescript" ? "TypeScript" : info.language === "javascript" ? "JavaScript" : info.language;
|
|
31843
|
+
const frameworkLabel = info.framework ? `${info.framework} ` : "";
|
|
31844
|
+
const monoLabel = info.monorepo ? " (monorepo)" : "";
|
|
31845
|
+
const lines = [];
|
|
31846
|
+
lines.push("# AGENTS.md");
|
|
31847
|
+
lines.push("");
|
|
31848
|
+
lines.push(`${frameworkLabel}${lang} project${monoLabel}.`);
|
|
31849
|
+
lines.push("");
|
|
31850
|
+
const scriptEntries = Object.entries(info.scripts);
|
|
31851
|
+
if (scriptEntries.length > 0 && info.packageManager) {
|
|
31852
|
+
const prefix = runCmd(info.packageManager);
|
|
31853
|
+
lines.push("## Commands");
|
|
31854
|
+
lines.push("");
|
|
31855
|
+
lines.push("```bash");
|
|
31856
|
+
const commands = scriptEntries.map(([name]) => `${prefix} ${name}`);
|
|
31857
|
+
const maxLen = Math.max(...commands.map((c) => c.length));
|
|
31858
|
+
for (let i = 0;i < scriptEntries.length; i++) {
|
|
31859
|
+
const [name] = scriptEntries[i];
|
|
31860
|
+
const cmd = commands[i];
|
|
31861
|
+
const desc = describeScript(name);
|
|
31862
|
+
if (desc) {
|
|
31863
|
+
lines.push(`${cmd}${" ".repeat(maxLen - cmd.length + 4)}# ${desc}`);
|
|
31864
|
+
} else {
|
|
31865
|
+
lines.push(cmd);
|
|
31866
|
+
}
|
|
31867
|
+
}
|
|
31868
|
+
lines.push("```");
|
|
31869
|
+
lines.push("");
|
|
31870
|
+
}
|
|
31871
|
+
lines.push("## Code Standards");
|
|
31872
|
+
lines.push("");
|
|
31873
|
+
const langLabel = info.language === "typescript" ? "TypeScript" : "JavaScript";
|
|
31874
|
+
if (info.language === "typescript" || info.language === "javascript") {
|
|
31875
|
+
lines.push(`- ${langLabel} with ES modules`);
|
|
31876
|
+
}
|
|
31877
|
+
if (info.indentStyle) {
|
|
31878
|
+
const unit = info.indentStyle.type === "tab" ? "tab" : "space";
|
|
31879
|
+
lines.push(`- ${info.indentStyle.width}-${unit} indentation`);
|
|
31880
|
+
}
|
|
31881
|
+
if (info.linter) {
|
|
31882
|
+
lines.push(`- Linted with ${info.linter}`);
|
|
31883
|
+
}
|
|
31884
|
+
lines.push("");
|
|
31885
|
+
lines.push("## Architecture");
|
|
31886
|
+
lines.push("");
|
|
31887
|
+
for (const dir of info.dirs) {
|
|
31888
|
+
lines.push(`- \`${dir}/\` — ${describeDir(dir)}`);
|
|
31889
|
+
}
|
|
31890
|
+
if (info.srcDirs.length > 0) {
|
|
31891
|
+
for (const sub of info.srcDirs) {
|
|
31892
|
+
lines.push(` - \`src/${sub}/\` — ${describeDir(sub)}`);
|
|
31893
|
+
}
|
|
31894
|
+
}
|
|
31895
|
+
lines.push("");
|
|
31896
|
+
return lines.join(`
|
|
31897
|
+
`);
|
|
31898
|
+
}
|
|
31899
|
+
function generateClaudeMd(info) {
|
|
31900
|
+
const lines = [];
|
|
31901
|
+
lines.push("# CLAUDE.md");
|
|
31902
|
+
lines.push("");
|
|
31903
|
+
lines.push("@AGENTS.md");
|
|
31904
|
+
if (info.existingDocs.architectureMd || info.dirs.includes("docs")) {
|
|
31905
|
+
lines.push("@docs/architecture.md");
|
|
31906
|
+
}
|
|
31907
|
+
lines.push("");
|
|
31908
|
+
return lines.join(`
|
|
31909
|
+
`);
|
|
31910
|
+
}
|
|
31911
|
+
function generateArchitectureMd(info, _cwd) {
|
|
31912
|
+
const lines = [];
|
|
31913
|
+
lines.push("# Architecture");
|
|
31914
|
+
lines.push("");
|
|
31915
|
+
lines.push("## Directory Structure");
|
|
31916
|
+
lines.push("");
|
|
31917
|
+
for (const dir of info.dirs) {
|
|
31918
|
+
lines.push(`- \`${dir}/\` — ${describeDir(dir)}`);
|
|
31919
|
+
}
|
|
31920
|
+
if (info.srcDirs.length > 0) {
|
|
31921
|
+
lines.push("");
|
|
31922
|
+
lines.push("### `src/`");
|
|
31923
|
+
lines.push("");
|
|
31924
|
+
for (const sub of info.srcDirs) {
|
|
31925
|
+
lines.push(`- \`src/${sub}/\` — ${describeDir(sub)}`);
|
|
31926
|
+
}
|
|
31927
|
+
}
|
|
31928
|
+
lines.push("");
|
|
31929
|
+
return lines.join(`
|
|
31930
|
+
`);
|
|
31931
|
+
}
|
|
31932
|
+
var VAGUE_STANDARDS = [
|
|
31933
|
+
"follow best practices",
|
|
31934
|
+
"use best practices",
|
|
31935
|
+
"keep it clean",
|
|
31936
|
+
"write clean code",
|
|
31937
|
+
"maintain code quality",
|
|
31938
|
+
"ensure quality",
|
|
31939
|
+
"use proper naming",
|
|
31940
|
+
"follow conventions",
|
|
31941
|
+
"be consistent"
|
|
31942
|
+
];
|
|
31943
|
+
function verifyDocs(cwd) {
|
|
31944
|
+
const issues = [];
|
|
31945
|
+
const claudeMd = readText(join4(cwd, "CLAUDE.md"));
|
|
31946
|
+
const agentsMd = readText(join4(cwd, "AGENTS.md"));
|
|
31947
|
+
const pkg = readJson(join4(cwd, "package.json"));
|
|
31948
|
+
const pkgScripts = pkg && typeof pkg.scripts === "object" && pkg.scripts !== null ? pkg.scripts : {};
|
|
31949
|
+
const projectRoot = resolve(cwd);
|
|
31950
|
+
if (claudeMd) {
|
|
31951
|
+
const importedFiles = [];
|
|
31952
|
+
for (const line of claudeMd.split(`
|
|
31953
|
+
`)) {
|
|
31954
|
+
const match = line.match(/^@(.+)$/);
|
|
31955
|
+
if (match) {
|
|
31956
|
+
const refPath = match[1].trim();
|
|
31957
|
+
if (isAbsolute(refPath)) {
|
|
31958
|
+
issues.push({
|
|
31959
|
+
severity: "error",
|
|
31960
|
+
file: "CLAUDE.md",
|
|
31961
|
+
message: `@ reference uses an absolute path: ${refPath}`,
|
|
31962
|
+
fix: "Use a project-relative path under the repository root"
|
|
31963
|
+
});
|
|
31964
|
+
continue;
|
|
31965
|
+
}
|
|
31966
|
+
const resolvedPath = resolve(projectRoot, refPath);
|
|
31967
|
+
if (resolvedPath !== projectRoot && !resolvedPath.startsWith(projectRoot + sep2)) {
|
|
31968
|
+
issues.push({
|
|
31969
|
+
severity: "error",
|
|
31970
|
+
file: "CLAUDE.md",
|
|
31971
|
+
message: `@ reference escapes project root: ${refPath}`,
|
|
31972
|
+
fix: "Remove traversal segments (../) and keep references inside the repo"
|
|
31973
|
+
});
|
|
31974
|
+
continue;
|
|
31975
|
+
}
|
|
31976
|
+
importedFiles.push({ ref: refPath, resolved: resolvedPath });
|
|
31977
|
+
if (!existsSync4(resolvedPath)) {
|
|
31978
|
+
issues.push({
|
|
31979
|
+
severity: "error",
|
|
31980
|
+
file: "CLAUDE.md",
|
|
31981
|
+
message: `Referenced file does not exist: ${refPath}`,
|
|
31982
|
+
fix: `Remove the @${refPath} line or create the file`
|
|
31983
|
+
});
|
|
31984
|
+
}
|
|
31985
|
+
}
|
|
31986
|
+
}
|
|
31987
|
+
const claudeLines = claudeMd.split(`
|
|
31988
|
+
`).length;
|
|
31989
|
+
if (claudeLines > 100) {
|
|
31990
|
+
issues.push({
|
|
31991
|
+
severity: "warning",
|
|
31992
|
+
file: "CLAUDE.md",
|
|
31993
|
+
message: `CLAUDE.md is ${claudeLines} lines (recommended: under 100)`,
|
|
31994
|
+
fix: "Move detailed content to AGENTS.md or docs/ files and use @imports"
|
|
31995
|
+
});
|
|
31996
|
+
}
|
|
31997
|
+
if (importedFiles.length > 0) {
|
|
31998
|
+
const claudeHeadings = extractHeadings(claudeMd);
|
|
31999
|
+
for (const { ref: refPath, resolved: resolvedPath } of importedFiles) {
|
|
32000
|
+
const refContent = readText(resolvedPath);
|
|
32001
|
+
if (!refContent)
|
|
32002
|
+
continue;
|
|
32003
|
+
const refHeadings = extractHeadings(refContent);
|
|
32004
|
+
for (const heading of claudeHeadings) {
|
|
32005
|
+
if (refHeadings.has(heading)) {
|
|
32006
|
+
issues.push({
|
|
32007
|
+
severity: "warning",
|
|
32008
|
+
file: "CLAUDE.md",
|
|
32009
|
+
message: `Section "${heading}" duplicates content from @${refPath}`,
|
|
32010
|
+
fix: `Remove the "${heading}" section — it's already included via @import`
|
|
32011
|
+
});
|
|
32012
|
+
}
|
|
32013
|
+
}
|
|
32014
|
+
}
|
|
32015
|
+
}
|
|
32016
|
+
}
|
|
32017
|
+
if (agentsMd) {
|
|
32018
|
+
const agentsLines = agentsMd.split(`
|
|
32019
|
+
`);
|
|
32020
|
+
const contextLines = [];
|
|
32021
|
+
let pastFirstHeading = false;
|
|
32022
|
+
let hitNextSection = false;
|
|
32023
|
+
for (const line of agentsLines) {
|
|
32024
|
+
if (!pastFirstHeading) {
|
|
32025
|
+
if (line.startsWith("# ")) {
|
|
32026
|
+
pastFirstHeading = true;
|
|
32027
|
+
}
|
|
32028
|
+
continue;
|
|
32029
|
+
}
|
|
32030
|
+
if (line.startsWith("## ")) {
|
|
32031
|
+
hitNextSection = true;
|
|
32032
|
+
break;
|
|
32033
|
+
}
|
|
32034
|
+
const trimmed = line.trim();
|
|
32035
|
+
if (trimmed)
|
|
32036
|
+
contextLines.push(trimmed);
|
|
32037
|
+
}
|
|
32038
|
+
if (pastFirstHeading && !hitNextSection && contextLines.length === 0) {
|
|
32039
|
+
issues.push({
|
|
32040
|
+
severity: "warning",
|
|
32041
|
+
file: "AGENTS.md",
|
|
32042
|
+
message: "Missing project context line after the title heading",
|
|
32043
|
+
fix: "Add a single-line description: stack + what the project does"
|
|
32044
|
+
});
|
|
32045
|
+
} else if (contextLines.length > 1) {
|
|
32046
|
+
issues.push({
|
|
32047
|
+
severity: "warning",
|
|
32048
|
+
file: "AGENTS.md",
|
|
32049
|
+
message: `Project context should be exactly 1 line, found ${contextLines.length}`,
|
|
32050
|
+
fix: "Condense to a single line: stack + what the project does"
|
|
32051
|
+
});
|
|
32052
|
+
}
|
|
32053
|
+
const codeBlockRe = /```[\s\S]*?```/g;
|
|
32054
|
+
let blockMatch;
|
|
32055
|
+
while ((blockMatch = codeBlockRe.exec(agentsMd)) !== null) {
|
|
32056
|
+
const block = blockMatch[0];
|
|
32057
|
+
const cmdRe = /(?:bun|npm|pnpm|yarn)\s+(?:run\s+)?(\S+)/g;
|
|
32058
|
+
let cmdMatch;
|
|
32059
|
+
while ((cmdMatch = cmdRe.exec(block)) !== null) {
|
|
32060
|
+
const scriptName = cmdMatch[1];
|
|
32061
|
+
const builtins = new Set([
|
|
32062
|
+
"install",
|
|
32063
|
+
"init",
|
|
32064
|
+
"create",
|
|
32065
|
+
"exec",
|
|
32066
|
+
"dlx",
|
|
32067
|
+
"x",
|
|
32068
|
+
"test",
|
|
32069
|
+
"start"
|
|
32070
|
+
]);
|
|
32071
|
+
if (builtins.has(scriptName))
|
|
32072
|
+
continue;
|
|
32073
|
+
if (Object.keys(pkgScripts).length > 0 && !(scriptName in pkgScripts)) {
|
|
32074
|
+
issues.push({
|
|
32075
|
+
severity: "warning",
|
|
32076
|
+
file: "AGENTS.md",
|
|
32077
|
+
message: `Command references script "${scriptName}" which is not in package.json`,
|
|
32078
|
+
fix: `Update the command or add "${scriptName}" to package.json scripts`
|
|
32079
|
+
});
|
|
32080
|
+
}
|
|
32081
|
+
}
|
|
32082
|
+
}
|
|
32083
|
+
const standardsSection = extractSection(agentsMd, "Code Standards");
|
|
32084
|
+
if (standardsSection) {
|
|
32085
|
+
const lower = standardsSection.toLowerCase();
|
|
32086
|
+
for (const phrase of VAGUE_STANDARDS) {
|
|
32087
|
+
if (lower.includes(phrase)) {
|
|
32088
|
+
issues.push({
|
|
32089
|
+
severity: "warning",
|
|
32090
|
+
file: "AGENTS.md",
|
|
32091
|
+
message: `Code Standards contains vague phrase: "${phrase}"`,
|
|
32092
|
+
fix: "Replace with specific, verifiable conventions derived from config files"
|
|
32093
|
+
});
|
|
32094
|
+
}
|
|
32095
|
+
}
|
|
32096
|
+
}
|
|
32097
|
+
if (Object.keys(pkgScripts).length > 0) {
|
|
32098
|
+
const hasTestScript = Object.keys(pkgScripts).some((k3) => k3 === "test" || k3.startsWith("test:"));
|
|
32099
|
+
if (!hasTestScript) {
|
|
32100
|
+
const mentionsNoTest = agentsMd.toLowerCase().includes("no test");
|
|
32101
|
+
if (!mentionsNoTest) {
|
|
32102
|
+
issues.push({
|
|
32103
|
+
severity: "warning",
|
|
32104
|
+
file: "AGENTS.md",
|
|
32105
|
+
message: "No test script in package.json and AGENTS.md doesn't mention it",
|
|
32106
|
+
fix: 'Add a note like "No test framework. Verify changes with `bun run build`."'
|
|
32107
|
+
});
|
|
32108
|
+
}
|
|
32109
|
+
}
|
|
32110
|
+
}
|
|
32111
|
+
checkBacktickPaths(agentsMd, "AGENTS.md", cwd, issues);
|
|
32112
|
+
}
|
|
32113
|
+
const archMd = readText(join4(cwd, "docs", "architecture.md"));
|
|
32114
|
+
if (archMd) {
|
|
32115
|
+
checkBacktickPaths(archMd, "docs/architecture.md", cwd, issues);
|
|
32116
|
+
}
|
|
32117
|
+
return issues;
|
|
32118
|
+
}
|
|
32119
|
+
function extractHeadings(content) {
|
|
32120
|
+
const headings = new Set;
|
|
32121
|
+
for (const line of content.split(`
|
|
32122
|
+
`)) {
|
|
32123
|
+
const match = line.match(/^#{2,3}\s+(.+)$/);
|
|
32124
|
+
if (match) {
|
|
32125
|
+
headings.add(match[1].trim());
|
|
32126
|
+
}
|
|
32127
|
+
}
|
|
32128
|
+
return headings;
|
|
32129
|
+
}
|
|
32130
|
+
function extractSection(content, heading) {
|
|
32131
|
+
const lines = content.split(`
|
|
32132
|
+
`);
|
|
32133
|
+
let capturing = false;
|
|
32134
|
+
const result = [];
|
|
32135
|
+
for (const line of lines) {
|
|
32136
|
+
if (capturing) {
|
|
32137
|
+
if (line.match(/^#{1,2}\s/))
|
|
32138
|
+
break;
|
|
32139
|
+
result.push(line);
|
|
32140
|
+
} else if (line.match(new RegExp(`^##\\s+${heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "i"))) {
|
|
32141
|
+
capturing = true;
|
|
32142
|
+
}
|
|
32143
|
+
}
|
|
32144
|
+
return result.length > 0 ? result.join(`
|
|
32145
|
+
`) : null;
|
|
32146
|
+
}
|
|
32147
|
+
function checkBacktickPaths(content, file2, cwd, issues) {
|
|
32148
|
+
const pathRe = /`((?:src\/|packages\/|apps\/|supabase\/|docs\/)[^`]+)`/g;
|
|
32149
|
+
let match;
|
|
32150
|
+
const checked = new Set;
|
|
32151
|
+
const root = resolve(cwd);
|
|
32152
|
+
while ((match = pathRe.exec(content)) !== null) {
|
|
32153
|
+
const refPath = match[1].replace(/\/$/, "");
|
|
32154
|
+
if (checked.has(refPath))
|
|
32155
|
+
continue;
|
|
32156
|
+
checked.add(refPath);
|
|
32157
|
+
const resolvedRef = resolve(root, refPath);
|
|
32158
|
+
if (resolvedRef !== root && !resolvedRef.startsWith(root + sep2))
|
|
32159
|
+
continue;
|
|
32160
|
+
if (!existsSync4(resolvedRef)) {
|
|
32161
|
+
issues.push({
|
|
32162
|
+
severity: "warning",
|
|
32163
|
+
file: file2,
|
|
32164
|
+
message: `Referenced path does not exist: ${refPath}`,
|
|
32165
|
+
fix: `Update or remove the \`${refPath}\` reference`
|
|
32166
|
+
});
|
|
32167
|
+
}
|
|
32168
|
+
}
|
|
32169
|
+
}
|
|
32170
|
+
async function runDocsStep(cwd) {
|
|
32171
|
+
const info = scanProject(cwd);
|
|
32172
|
+
const hasDocs = info.existingDocs.agentsMd || info.existingDocs.claudeMd;
|
|
32173
|
+
if (!hasDocs) {
|
|
32174
|
+
const shouldGenerate = await ye({
|
|
32175
|
+
message: "No project docs found. Generate AGENTS.md and CLAUDE.md?",
|
|
32176
|
+
initialValue: true
|
|
32177
|
+
});
|
|
32178
|
+
if (pD(shouldGenerate) || !shouldGenerate) {
|
|
32179
|
+
return { files: [], issues: [], skipped: true };
|
|
32180
|
+
}
|
|
32181
|
+
const files = [];
|
|
32182
|
+
files.push({
|
|
32183
|
+
path: join4(cwd, "AGENTS.md"),
|
|
32184
|
+
content: generateAgentsMd(info, cwd),
|
|
32185
|
+
type: "text"
|
|
32186
|
+
});
|
|
32187
|
+
files.push({
|
|
32188
|
+
path: join4(cwd, "CLAUDE.md"),
|
|
32189
|
+
content: generateClaudeMd(info),
|
|
32190
|
+
type: "text"
|
|
32191
|
+
});
|
|
32192
|
+
if (info.dirs.includes("docs") || info.srcDirs.length > 0) {
|
|
32193
|
+
files.push({
|
|
32194
|
+
path: join4(cwd, "docs", "architecture.md"),
|
|
32195
|
+
content: generateArchitectureMd(info, cwd),
|
|
32196
|
+
type: "text"
|
|
32197
|
+
});
|
|
32198
|
+
}
|
|
32199
|
+
M2.success(`Generated ${files.length} doc file(s): ${files.map((f) => f.path.replace(cwd + "/", "")).join(", ")}`);
|
|
32200
|
+
return { files, issues: [], skipped: false };
|
|
32201
|
+
}
|
|
32202
|
+
const shouldVerify = await ye({
|
|
32203
|
+
message: "Project docs found. Verify for issues?",
|
|
32204
|
+
initialValue: false
|
|
32205
|
+
});
|
|
32206
|
+
if (pD(shouldVerify) || !shouldVerify) {
|
|
32207
|
+
return { files: [], issues: [], skipped: true };
|
|
32208
|
+
}
|
|
32209
|
+
const issues = verifyDocs(cwd);
|
|
32210
|
+
if (issues.length === 0) {
|
|
32211
|
+
M2.success("No issues found in project docs.");
|
|
32212
|
+
} else {
|
|
32213
|
+
for (const issue2 of issues) {
|
|
32214
|
+
const prefix = `${colors.bold(issue2.file)}:`;
|
|
32215
|
+
if (issue2.severity === "error") {
|
|
32216
|
+
M2.error(`${prefix} ${issue2.message}`);
|
|
32217
|
+
} else {
|
|
32218
|
+
M2.warning(`${prefix} ${issue2.message}`);
|
|
32219
|
+
}
|
|
32220
|
+
if (issue2.fix) {
|
|
32221
|
+
M2.message(` ${symbols.arrow} ${colors.dim(issue2.fix)}`);
|
|
32222
|
+
}
|
|
32223
|
+
}
|
|
32224
|
+
M2.info(`Found ${issues.length} issue(s) (${issues.filter((i) => i.severity === "error").length} errors, ${issues.filter((i) => i.severity === "warning").length} warnings)`);
|
|
32225
|
+
}
|
|
32226
|
+
return { files: [], issues, skipped: false };
|
|
32227
|
+
}
|
|
32228
|
+
|
|
31586
32229
|
// src/tui/writer.ts
|
|
31587
|
-
import { existsSync as
|
|
32230
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
31588
32231
|
import { homedir as homedir3 } from "node:os";
|
|
31589
32232
|
import { dirname } from "node:path";
|
|
31590
32233
|
function ensureDir(dirPath) {
|
|
31591
|
-
if (!
|
|
32234
|
+
if (!existsSync5(dirPath)) {
|
|
31592
32235
|
mkdirSync3(dirPath, { recursive: true, mode: 493 });
|
|
31593
32236
|
}
|
|
31594
32237
|
}
|
|
31595
32238
|
function writeFile(filePath, content, options = {}) {
|
|
31596
|
-
const exists =
|
|
32239
|
+
const exists = existsSync5(filePath);
|
|
31597
32240
|
if (exists && !options.force) {
|
|
31598
32241
|
return { path: filePath, action: "skip" };
|
|
31599
32242
|
}
|
|
@@ -31611,7 +32254,7 @@ function writeFile(filePath, content, options = {}) {
|
|
|
31611
32254
|
}
|
|
31612
32255
|
}
|
|
31613
32256
|
function mergeJsonFile(filePath, updates, options = {}) {
|
|
31614
|
-
const exists =
|
|
32257
|
+
const exists = existsSync5(filePath);
|
|
31615
32258
|
if (!exists) {
|
|
31616
32259
|
try {
|
|
31617
32260
|
ensureDir(dirname(filePath));
|
|
@@ -31628,7 +32271,7 @@ function mergeJsonFile(filePath, updates, options = {}) {
|
|
|
31628
32271
|
}
|
|
31629
32272
|
}
|
|
31630
32273
|
try {
|
|
31631
|
-
const existing = JSON.parse(
|
|
32274
|
+
const existing = JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
31632
32275
|
if (updates.mcpServers && existing.mcpServers) {
|
|
31633
32276
|
const existingServers = existing.mcpServers;
|
|
31634
32277
|
const updateServers = updates.mcpServers;
|
|
@@ -31661,7 +32304,7 @@ function mergeJsonFile(filePath, updates, options = {}) {
|
|
|
31661
32304
|
}
|
|
31662
32305
|
}
|
|
31663
32306
|
function appendToToml(filePath, section, content, options = {}) {
|
|
31664
|
-
const exists =
|
|
32307
|
+
const exists = existsSync5(filePath);
|
|
31665
32308
|
if (!exists) {
|
|
31666
32309
|
try {
|
|
31667
32310
|
ensureDir(dirname(filePath));
|
|
@@ -31676,7 +32319,7 @@ function appendToToml(filePath, section, content, options = {}) {
|
|
|
31676
32319
|
}
|
|
31677
32320
|
}
|
|
31678
32321
|
try {
|
|
31679
|
-
const existing =
|
|
32322
|
+
const existing = readFileSync4(filePath, "utf-8");
|
|
31680
32323
|
if (existing.includes(section)) {
|
|
31681
32324
|
if (options.force) {
|
|
31682
32325
|
const updated = existing.replace(new RegExp(`\\[${section.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\][\\s\\S]*?(?=\\[|$)`), content.trim() + `
|
|
@@ -31714,7 +32357,7 @@ async function writeFilesWithProgress(files, options = {}) {
|
|
|
31714
32357
|
result = writeFile(file2.path, file2.content, options);
|
|
31715
32358
|
}
|
|
31716
32359
|
results.push(result);
|
|
31717
|
-
await new Promise((
|
|
32360
|
+
await new Promise((resolve2) => setTimeout(resolve2, 50));
|
|
31718
32361
|
}
|
|
31719
32362
|
spinner.stop("Files written");
|
|
31720
32363
|
for (const result of results) {
|
|
@@ -31737,7 +32380,7 @@ function getWriteSummary(files, options = {}) {
|
|
|
31737
32380
|
const home = homedir3();
|
|
31738
32381
|
for (const file2 of files) {
|
|
31739
32382
|
const displayPath = formatPath(file2.path, home);
|
|
31740
|
-
const exists =
|
|
32383
|
+
const exists = existsSync5(file2.path);
|
|
31741
32384
|
if (exists && !options.force) {
|
|
31742
32385
|
toSkip.push(displayPath);
|
|
31743
32386
|
} else if (exists) {
|
|
@@ -31750,7 +32393,7 @@ function getWriteSummary(files, options = {}) {
|
|
|
31750
32393
|
}
|
|
31751
32394
|
|
|
31752
32395
|
// src/tui/setup.ts
|
|
31753
|
-
var GLOBAL_SKILLS_DIR =
|
|
32396
|
+
var GLOBAL_SKILLS_DIR = join5(homedir4(), ".agents", "skills");
|
|
31754
32397
|
var API_URL = "https://gethmy.com/api";
|
|
31755
32398
|
var HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
|
|
31756
32399
|
|
|
@@ -32043,7 +32686,7 @@ Report the result:
|
|
|
32043
32686
|
async function registerMcpServer() {
|
|
32044
32687
|
try {
|
|
32045
32688
|
const { execSync } = await import("node:child_process");
|
|
32046
|
-
execSync("claude mcp add --transport stdio harmony -- npx -y
|
|
32689
|
+
execSync("claude mcp add --transport stdio harmony -- npx -y @gethmy/mcp@latest serve", {
|
|
32047
32690
|
stdio: "pipe"
|
|
32048
32691
|
});
|
|
32049
32692
|
return true;
|
|
@@ -32052,22 +32695,22 @@ async function registerMcpServer() {
|
|
|
32052
32695
|
}
|
|
32053
32696
|
}
|
|
32054
32697
|
async function writeMcpConfigFallback(home) {
|
|
32055
|
-
const { readFileSync:
|
|
32056
|
-
const settingsPath =
|
|
32698
|
+
const { readFileSync: readFileSync5, writeFileSync: writeFileSync4, mkdirSync: mkdirSync5, existsSync: existsSync7 } = await import("node:fs");
|
|
32699
|
+
const settingsPath = join5(home, ".claude", "settings.json");
|
|
32057
32700
|
const settingsDir = dirname2(settingsPath);
|
|
32058
|
-
if (!
|
|
32701
|
+
if (!existsSync7(settingsDir)) {
|
|
32059
32702
|
mkdirSync5(settingsDir, { recursive: true });
|
|
32060
32703
|
}
|
|
32061
32704
|
let settings = {};
|
|
32062
|
-
if (
|
|
32705
|
+
if (existsSync7(settingsPath)) {
|
|
32063
32706
|
try {
|
|
32064
|
-
settings = JSON.parse(
|
|
32707
|
+
settings = JSON.parse(readFileSync5(settingsPath, "utf-8"));
|
|
32065
32708
|
} catch {}
|
|
32066
32709
|
}
|
|
32067
32710
|
const mcpServers = settings.mcpServers || {};
|
|
32068
32711
|
mcpServers.harmony = {
|
|
32069
32712
|
command: "npx",
|
|
32070
|
-
args: ["-y", "
|
|
32713
|
+
args: ["-y", "@gethmy/mcp@latest", "serve"]
|
|
32071
32714
|
};
|
|
32072
32715
|
settings.mcpServers = mcpServers;
|
|
32073
32716
|
writeFileSync4(settingsPath, JSON.stringify(settings, null, 2));
|
|
@@ -32159,31 +32802,31 @@ ${HARMONY_PLAN_PROMPT.replace("$ARGUMENTS", "$ARGUMENTS")}
|
|
|
32159
32802
|
`;
|
|
32160
32803
|
if (installMode === "global") {
|
|
32161
32804
|
files.push({
|
|
32162
|
-
path:
|
|
32805
|
+
path: join5(GLOBAL_SKILLS_DIR, "hmy", "SKILL.md"),
|
|
32163
32806
|
content: skillContent,
|
|
32164
32807
|
type: "text"
|
|
32165
32808
|
});
|
|
32166
32809
|
symlinks.push({
|
|
32167
|
-
target:
|
|
32168
|
-
link:
|
|
32810
|
+
target: join5(GLOBAL_SKILLS_DIR, "hmy"),
|
|
32811
|
+
link: join5(home, ".claude", "skills", "hmy")
|
|
32169
32812
|
});
|
|
32170
32813
|
files.push({
|
|
32171
|
-
path:
|
|
32814
|
+
path: join5(GLOBAL_SKILLS_DIR, "hmy-plan", "SKILL.md"),
|
|
32172
32815
|
content: planSkillContent,
|
|
32173
32816
|
type: "text"
|
|
32174
32817
|
});
|
|
32175
32818
|
symlinks.push({
|
|
32176
|
-
target:
|
|
32177
|
-
link:
|
|
32819
|
+
target: join5(GLOBAL_SKILLS_DIR, "hmy-plan"),
|
|
32820
|
+
link: join5(home, ".claude", "skills", "hmy-plan")
|
|
32178
32821
|
});
|
|
32179
32822
|
} else {
|
|
32180
32823
|
files.push({
|
|
32181
|
-
path:
|
|
32824
|
+
path: join5(cwd, ".claude", "skills", "hmy", "SKILL.md"),
|
|
32182
32825
|
content: skillContent,
|
|
32183
32826
|
type: "text"
|
|
32184
32827
|
});
|
|
32185
32828
|
files.push({
|
|
32186
|
-
path:
|
|
32829
|
+
path: join5(cwd, ".claude", "skills", "hmy-plan", "SKILL.md"),
|
|
32187
32830
|
content: planSkillContent,
|
|
32188
32831
|
type: "text"
|
|
32189
32832
|
});
|
|
@@ -32218,7 +32861,7 @@ When given a card reference (e.g., #42 or a card name), follow this workflow:
|
|
|
32218
32861
|
- \`harmony_generate_prompt\` - Get role-based guidance and focus areas for the card
|
|
32219
32862
|
`;
|
|
32220
32863
|
files.push({
|
|
32221
|
-
path:
|
|
32864
|
+
path: join5(cwd, "AGENTS.md"),
|
|
32222
32865
|
content: agentsContent,
|
|
32223
32866
|
type: "text"
|
|
32224
32867
|
});
|
|
@@ -32235,17 +32878,17 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "{{card}}").replace("Your agent
|
|
|
32235
32878
|
`;
|
|
32236
32879
|
if (installMode === "global") {
|
|
32237
32880
|
files.push({
|
|
32238
|
-
path:
|
|
32881
|
+
path: join5(GLOBAL_SKILLS_DIR, "codex", "hmy.md"),
|
|
32239
32882
|
content: promptContent,
|
|
32240
32883
|
type: "text"
|
|
32241
32884
|
});
|
|
32242
32885
|
symlinks.push({
|
|
32243
|
-
target:
|
|
32244
|
-
link:
|
|
32886
|
+
target: join5(GLOBAL_SKILLS_DIR, "codex", "hmy.md"),
|
|
32887
|
+
link: join5(home, ".codex", "prompts", "hmy.md")
|
|
32245
32888
|
});
|
|
32246
32889
|
} else {
|
|
32247
32890
|
files.push({
|
|
32248
|
-
path:
|
|
32891
|
+
path: join5(home, ".codex", "prompts", "hmy.md"),
|
|
32249
32892
|
content: promptContent,
|
|
32250
32893
|
type: "text"
|
|
32251
32894
|
});
|
|
@@ -32254,10 +32897,10 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "{{card}}").replace("Your agent
|
|
|
32254
32897
|
# Harmony MCP Server
|
|
32255
32898
|
[mcp_servers.harmony]
|
|
32256
32899
|
command = "npx"
|
|
32257
|
-
args = ["-y", "
|
|
32900
|
+
args = ["-y", "@gethmy/mcp@latest", "serve"]
|
|
32258
32901
|
`;
|
|
32259
32902
|
files.push({
|
|
32260
|
-
path:
|
|
32903
|
+
path: join5(home, ".codex", "config.toml"),
|
|
32261
32904
|
content: tomlContent,
|
|
32262
32905
|
type: "toml",
|
|
32263
32906
|
tomlSection: "mcp_servers.harmony"
|
|
@@ -32266,12 +32909,12 @@ args = ["-y", "harmony-mcp@latest", "serve"]
|
|
|
32266
32909
|
}
|
|
32267
32910
|
case "cursor": {
|
|
32268
32911
|
files.push({
|
|
32269
|
-
path:
|
|
32912
|
+
path: join5(cwd, ".cursor", "mcp.json"),
|
|
32270
32913
|
content: JSON.stringify({
|
|
32271
32914
|
mcpServers: {
|
|
32272
32915
|
harmony: {
|
|
32273
32916
|
command: "npx",
|
|
32274
|
-
args: ["-y", "
|
|
32917
|
+
args: ["-y", "@gethmy/mcp@latest", "serve"]
|
|
32275
32918
|
}
|
|
32276
32919
|
}
|
|
32277
32920
|
}, null, 2),
|
|
@@ -32292,17 +32935,17 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Y
|
|
|
32292
32935
|
`;
|
|
32293
32936
|
if (installMode === "global") {
|
|
32294
32937
|
files.push({
|
|
32295
|
-
path:
|
|
32938
|
+
path: join5(GLOBAL_SKILLS_DIR, "cursor", "harmony.mdc"),
|
|
32296
32939
|
content: ruleContent,
|
|
32297
32940
|
type: "text"
|
|
32298
32941
|
});
|
|
32299
32942
|
symlinks.push({
|
|
32300
|
-
target:
|
|
32301
|
-
link:
|
|
32943
|
+
target: join5(GLOBAL_SKILLS_DIR, "cursor", "harmony.mdc"),
|
|
32944
|
+
link: join5(home, ".cursor", "rules", "harmony.mdc")
|
|
32302
32945
|
});
|
|
32303
32946
|
} else {
|
|
32304
32947
|
files.push({
|
|
32305
|
-
path:
|
|
32948
|
+
path: join5(cwd, ".cursor", "rules", "harmony.mdc"),
|
|
32306
32949
|
content: ruleContent,
|
|
32307
32950
|
type: "text"
|
|
32308
32951
|
});
|
|
@@ -32311,12 +32954,12 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Y
|
|
|
32311
32954
|
}
|
|
32312
32955
|
case "windsurf": {
|
|
32313
32956
|
files.push({
|
|
32314
|
-
path:
|
|
32957
|
+
path: join5(home, ".codeium", "windsurf", "mcp_config.json"),
|
|
32315
32958
|
content: JSON.stringify({
|
|
32316
32959
|
mcpServers: {
|
|
32317
32960
|
harmony: {
|
|
32318
32961
|
command: "npx",
|
|
32319
|
-
args: ["-y", "
|
|
32962
|
+
args: ["-y", "@gethmy/mcp@latest", "serve"],
|
|
32320
32963
|
disabled: false,
|
|
32321
32964
|
alwaysAllow: []
|
|
32322
32965
|
}
|
|
@@ -32337,17 +32980,17 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Y
|
|
|
32337
32980
|
`;
|
|
32338
32981
|
if (installMode === "global") {
|
|
32339
32982
|
files.push({
|
|
32340
|
-
path:
|
|
32983
|
+
path: join5(GLOBAL_SKILLS_DIR, "windsurf", "harmony.md"),
|
|
32341
32984
|
content: ruleContent,
|
|
32342
32985
|
type: "text"
|
|
32343
32986
|
});
|
|
32344
32987
|
symlinks.push({
|
|
32345
|
-
target:
|
|
32346
|
-
link:
|
|
32988
|
+
target: join5(GLOBAL_SKILLS_DIR, "windsurf", "harmony.md"),
|
|
32989
|
+
link: join5(home, ".codeium", "windsurf", "rules", "harmony.md")
|
|
32347
32990
|
});
|
|
32348
32991
|
} else {
|
|
32349
32992
|
files.push({
|
|
32350
|
-
path:
|
|
32993
|
+
path: join5(cwd, ".windsurf", "rules", "harmony.md"),
|
|
32351
32994
|
content: ruleContent,
|
|
32352
32995
|
type: "text"
|
|
32353
32996
|
});
|
|
@@ -32548,6 +33191,14 @@ async function runSetup(options = {}) {
|
|
|
32548
33191
|
type: "text"
|
|
32549
33192
|
});
|
|
32550
33193
|
}
|
|
33194
|
+
if (!options.skipDocs) {
|
|
33195
|
+
const docsResult = await runDocsStep(cwd);
|
|
33196
|
+
if (!docsResult.skipped) {
|
|
33197
|
+
for (const file2 of docsResult.files) {
|
|
33198
|
+
allFiles.push(file2);
|
|
33199
|
+
}
|
|
33200
|
+
}
|
|
33201
|
+
}
|
|
32551
33202
|
if (needsSkills && selectedAgents.length > 0) {
|
|
32552
33203
|
for (const agentId of selectedAgents) {
|
|
32553
33204
|
const { files, symlinks } = getAgentFiles(agentId, cwd, installMode);
|
|
@@ -32623,7 +33274,7 @@ async function runSetup(options = {}) {
|
|
|
32623
33274
|
for (const symlink of allSymlinks) {
|
|
32624
33275
|
try {
|
|
32625
33276
|
const linkDir = dirname2(symlink.link);
|
|
32626
|
-
if (!
|
|
33277
|
+
if (!existsSync6(linkDir)) {
|
|
32627
33278
|
mkdirSync4(linkDir, { recursive: true });
|
|
32628
33279
|
}
|
|
32629
33280
|
let linkExists = false;
|
|
@@ -32652,9 +33303,9 @@ async function runSetup(options = {}) {
|
|
|
32652
33303
|
} else {
|
|
32653
33304
|
try {
|
|
32654
33305
|
await writeMcpConfigFallback(home);
|
|
32655
|
-
console.log(` ${colors.success("✓")} ${colors.dim(formatPath(
|
|
33306
|
+
console.log(` ${colors.success("✓")} ${colors.dim(formatPath(join5(home, ".claude", "settings.json"), home))} ${colors.dim("(updated)")}`);
|
|
32656
33307
|
} catch {
|
|
32657
|
-
M2.warning("Could not register MCP server. Run manually: claude mcp add --transport stdio harmony -- npx -y
|
|
33308
|
+
M2.warning("Could not register MCP server. Run manually: claude mcp add --transport stdio harmony -- npx -y @gethmy/mcp@latest serve");
|
|
32658
33309
|
}
|
|
32659
33310
|
}
|
|
32660
33311
|
}
|
|
@@ -32690,7 +33341,7 @@ async function runSetup(options = {}) {
|
|
|
32690
33341
|
console.log(` ${colors.brand("Cursor/Windsurf:")} MCP tools available automatically`);
|
|
32691
33342
|
}
|
|
32692
33343
|
console.log("");
|
|
32693
|
-
console.log(` ${colors.dim("Add to new project: npx
|
|
33344
|
+
console.log(` ${colors.dim("Add to new project: npx @gethmy/mcp setup")}`);
|
|
32694
33345
|
console.log(` ${colors.dim("Need help? Visit https://gethmy.com/docs/mcp")}`);
|
|
32695
33346
|
console.log("");
|
|
32696
33347
|
}
|
|
@@ -32698,7 +33349,7 @@ async function runSetup(options = {}) {
|
|
|
32698
33349
|
// src/cli.ts
|
|
32699
33350
|
var require2 = createRequire2(import.meta.url);
|
|
32700
33351
|
var { version: version2 } = require2("../package.json");
|
|
32701
|
-
program.name("
|
|
33352
|
+
program.name("@gethmy/mcp").description("MCP server for Harmony Kanban board").version(version2);
|
|
32702
33353
|
program.command("serve").description("Start the MCP server (stdio transport)").action(async () => {
|
|
32703
33354
|
const server = new HarmonyMCPServer;
|
|
32704
33355
|
await server.run();
|
|
@@ -32730,7 +33381,7 @@ Skills:`);
|
|
|
32730
33381
|
}
|
|
32731
33382
|
} else {
|
|
32732
33383
|
console.log(" Installed: No");
|
|
32733
|
-
console.log(" Run: npx
|
|
33384
|
+
console.log(" Run: npx @gethmy/mcp setup");
|
|
32734
33385
|
}
|
|
32735
33386
|
console.log(`
|
|
32736
33387
|
Context:`);
|
|
@@ -32753,7 +33404,7 @@ Context:`);
|
|
|
32753
33404
|
} else {
|
|
32754
33405
|
console.log(`Status: Not configured
|
|
32755
33406
|
`);
|
|
32756
|
-
console.log("Run: npx
|
|
33407
|
+
console.log("Run: npx @gethmy/mcp setup");
|
|
32757
33408
|
console.log("Get an API key at: https://gethmy.com/user/keys");
|
|
32758
33409
|
}
|
|
32759
33410
|
});
|
|
@@ -32766,9 +33417,9 @@ program.command("reset").description("Remove stored configuration").action(() =>
|
|
|
32766
33417
|
});
|
|
32767
33418
|
console.log("Configuration reset successfully");
|
|
32768
33419
|
console.log(`
|
|
32769
|
-
To reconfigure, run: npx
|
|
33420
|
+
To reconfigure, run: npx @gethmy/mcp setup`);
|
|
32770
33421
|
});
|
|
32771
|
-
program.command("setup").description("Smart setup wizard for Harmony MCP (recommended)").option("-f, --force", "Overwrite existing configuration files").option("-k, --api-key <key>", "API key (skips prompt)").option("-e, --email <email>", "Your email for auto-assignment").option("-a, --agents <agents...>", "Agents to configure: claude, codex, cursor, windsurf").option("-l, --local", "Install skills locally in project directory").option("-g, --global", "Install skills globally (recommended)").option("-w, --workspace <id>", "Set workspace context").option("-p, --project <id>", "Set project context").option("--skip-context", "Skip workspace/project selection").action(async (options) => {
|
|
33422
|
+
program.command("setup").description("Smart setup wizard for Harmony MCP (recommended)").option("-f, --force", "Overwrite existing configuration files").option("-k, --api-key <key>", "API key (skips prompt)").option("-e, --email <email>", "Your email for auto-assignment").option("-a, --agents <agents...>", "Agents to configure: claude, codex, cursor, windsurf").option("-l, --local", "Install skills locally in project directory").option("-g, --global", "Install skills globally (recommended)").option("-w, --workspace <id>", "Set workspace context").option("-p, --project <id>", "Set project context").option("--skip-context", "Skip workspace/project selection").option("--skip-docs", "Skip project docs scaffold/verification").action(async (options) => {
|
|
32772
33423
|
await runSetup({
|
|
32773
33424
|
force: options.force,
|
|
32774
33425
|
apiKey: options.apiKey,
|
|
@@ -32777,7 +33428,8 @@ program.command("setup").description("Smart setup wizard for Harmony MCP (recomm
|
|
|
32777
33428
|
installMode: options.global ? "global" : options.local ? "local" : undefined,
|
|
32778
33429
|
workspaceId: options.workspace,
|
|
32779
33430
|
projectId: options.project,
|
|
32780
|
-
skipContext: options.skipContext
|
|
33431
|
+
skipContext: options.skipContext,
|
|
33432
|
+
skipDocs: options.skipDocs
|
|
32781
33433
|
});
|
|
32782
33434
|
});
|
|
32783
33435
|
program.parse();
|