@gethmy/mcp 2.5.6 → 2.5.7
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/dist/cli.js +69 -5
- package/package.json +1 -1
- package/src/cli.ts +8 -3
- package/src/tui/setup.ts +92 -2
- package/src/tui/writer.ts +18 -4
package/dist/cli.js
CHANGED
|
@@ -4957,6 +4957,7 @@ async function refreshSkills() {
|
|
|
4957
4957
|
}
|
|
4958
4958
|
|
|
4959
4959
|
// src/tui/setup.ts
|
|
4960
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
4960
4961
|
import {
|
|
4961
4962
|
existsSync as existsSync7,
|
|
4962
4963
|
lstatSync,
|
|
@@ -5729,7 +5730,13 @@ async function runDocsStep(cwd) {
|
|
|
5729
5730
|
}
|
|
5730
5731
|
|
|
5731
5732
|
// src/tui/writer.ts
|
|
5732
|
-
import {
|
|
5733
|
+
import {
|
|
5734
|
+
chmodSync,
|
|
5735
|
+
existsSync as existsSync6,
|
|
5736
|
+
mkdirSync as mkdirSync4,
|
|
5737
|
+
readFileSync as readFileSync5,
|
|
5738
|
+
writeFileSync as writeFileSync4
|
|
5739
|
+
} from "node:fs";
|
|
5733
5740
|
import { homedir as homedir3 } from "node:os";
|
|
5734
5741
|
import { dirname as dirname2 } from "node:path";
|
|
5735
5742
|
import * as p2 from "@clack/prompts";
|
|
@@ -5745,8 +5752,12 @@ function writeFile(filePath, content, options = {}) {
|
|
|
5745
5752
|
}
|
|
5746
5753
|
try {
|
|
5747
5754
|
ensureDir(dirname2(filePath));
|
|
5748
|
-
const
|
|
5755
|
+
const defaultMode = filePath.includes(".harmony-mcp") ? 384 : 420;
|
|
5756
|
+
const mode = options.mode ?? defaultMode;
|
|
5749
5757
|
writeFileSync4(filePath, content, { mode });
|
|
5758
|
+
if (options.mode !== undefined) {
|
|
5759
|
+
chmodSync(filePath, options.mode);
|
|
5760
|
+
}
|
|
5750
5761
|
return { path: filePath, action: exists ? "update" : "create" };
|
|
5751
5762
|
} catch (error) {
|
|
5752
5763
|
return {
|
|
@@ -5857,7 +5868,10 @@ async function writeFilesWithProgress(files, options = {}) {
|
|
|
5857
5868
|
} else if (file.type === "toml" && file.tomlSection) {
|
|
5858
5869
|
result = appendToToml(file.path, file.tomlSection, file.content, options);
|
|
5859
5870
|
} else {
|
|
5860
|
-
result = writeFile(file.path, file.content,
|
|
5871
|
+
result = writeFile(file.path, file.content, {
|
|
5872
|
+
...options,
|
|
5873
|
+
mode: file.mode
|
|
5874
|
+
});
|
|
5861
5875
|
}
|
|
5862
5876
|
results.push(result);
|
|
5863
5877
|
await new Promise((resolve2) => setTimeout(resolve2, 50));
|
|
@@ -5993,6 +6007,22 @@ async function fetchProjects(apiKey, workspaceId) {
|
|
|
5993
6007
|
const data = await response.json();
|
|
5994
6008
|
return data.projects || [];
|
|
5995
6009
|
}
|
|
6010
|
+
async function resolveProjectSlug(apiKey, slug) {
|
|
6011
|
+
const response = await fetch(`${API_URL}/v1/projects/resolve/${encodeURIComponent(slug)}`, {
|
|
6012
|
+
method: "GET",
|
|
6013
|
+
headers: {
|
|
6014
|
+
"Content-Type": "application/json",
|
|
6015
|
+
"X-API-Key": apiKey
|
|
6016
|
+
}
|
|
6017
|
+
});
|
|
6018
|
+
if (response.status === 404)
|
|
6019
|
+
return null;
|
|
6020
|
+
if (!response.ok) {
|
|
6021
|
+
throw new Error(`Failed to resolve project slug: ${response.status}`);
|
|
6022
|
+
}
|
|
6023
|
+
const data = await response.json();
|
|
6024
|
+
return { workspaceId: data.workspaceId, projectId: data.projectId };
|
|
6025
|
+
}
|
|
5996
6026
|
async function getAgentFiles(agentId, cwd, installMode = "global") {
|
|
5997
6027
|
const home = homedir4();
|
|
5998
6028
|
const files = [];
|
|
@@ -6035,6 +6065,24 @@ async function getAgentFiles(agentId, cwd, installMode = "global") {
|
|
|
6035
6065
|
throw new Error(`Failed to fetch ${skillFailures.length}/${installableNames.length} skill(s) from /v1/skills:
|
|
6036
6066
|
${summary}`);
|
|
6037
6067
|
}
|
|
6068
|
+
try {
|
|
6069
|
+
const updateCheckFetched = await client3.fetchSkill("hmy-update-check");
|
|
6070
|
+
const actualHash = createHash3("sha256").update(updateCheckFetched.content).digest("hex");
|
|
6071
|
+
if (actualHash !== updateCheckFetched.sha256) {
|
|
6072
|
+
throw new Error(`hmy-update-check integrity check failed: expected ${updateCheckFetched.sha256}, got ${actualHash}`);
|
|
6073
|
+
}
|
|
6074
|
+
files.push({
|
|
6075
|
+
path: join5(home, ".hmy", "bin", "hmy-update-check"),
|
|
6076
|
+
content: updateCheckFetched.content,
|
|
6077
|
+
type: "text",
|
|
6078
|
+
mode: 493
|
|
6079
|
+
});
|
|
6080
|
+
files.push({
|
|
6081
|
+
path: join5(home, ".hmy", "VERSION"),
|
|
6082
|
+
content: versionInfo.version,
|
|
6083
|
+
type: "text"
|
|
6084
|
+
});
|
|
6085
|
+
} catch {}
|
|
6038
6086
|
break;
|
|
6039
6087
|
}
|
|
6040
6088
|
case "codex": {
|
|
@@ -6227,7 +6275,7 @@ async function runSetup(options = {}) {
|
|
|
6227
6275
|
let needsApiKey = !alreadyConfigured;
|
|
6228
6276
|
let needsSkills = !skillsStatus.installed || options.force;
|
|
6229
6277
|
let needsContext = !hasContext && !options.skipContext;
|
|
6230
|
-
if (options.workspaceId || options.projectId) {
|
|
6278
|
+
if (options.workspaceId || options.projectId || options.projectSlug) {
|
|
6231
6279
|
needsContext = true;
|
|
6232
6280
|
}
|
|
6233
6281
|
let apiKey = options.apiKey || existingConfig.apiKey;
|
|
@@ -6449,6 +6497,21 @@ async function runSetup(options = {}) {
|
|
|
6449
6497
|
let selectedProjectId = selectedProjectIdFromSignup || options.projectId;
|
|
6450
6498
|
let selectedWorkspaceName = selectedWorkspaceNameFromSignup;
|
|
6451
6499
|
let selectedProjectName = selectedProjectNameFromSignup;
|
|
6500
|
+
if (options.projectSlug && apiKey && (!selectedWorkspaceId || !selectedProjectId)) {
|
|
6501
|
+
spinner3.start(`Resolving project slug "${options.projectSlug}"...`);
|
|
6502
|
+
try {
|
|
6503
|
+
const resolved = await resolveProjectSlug(apiKey, options.projectSlug);
|
|
6504
|
+
if (resolved) {
|
|
6505
|
+
selectedWorkspaceId = selectedWorkspaceId || resolved.workspaceId;
|
|
6506
|
+
selectedProjectId = selectedProjectId || resolved.projectId;
|
|
6507
|
+
spinner3.stop(colors.success(`Resolved "${options.projectSlug}"`));
|
|
6508
|
+
} else {
|
|
6509
|
+
spinner3.stop(colors.warning(`No project found for slug "${options.projectSlug}"`));
|
|
6510
|
+
}
|
|
6511
|
+
} catch (error) {
|
|
6512
|
+
spinner3.stop(colors.warning(`Could not resolve slug: ${error instanceof Error ? error.message : "unknown error"}`));
|
|
6513
|
+
}
|
|
6514
|
+
}
|
|
6452
6515
|
if (createdNewAccount) {
|
|
6453
6516
|
needsContext = false;
|
|
6454
6517
|
}
|
|
@@ -6775,13 +6838,14 @@ program.command("reset").description("Remove stored configuration").action(() =>
|
|
|
6775
6838
|
console.log(`
|
|
6776
6839
|
To reconfigure, run: npx @gethmy/mcp setup`);
|
|
6777
6840
|
});
|
|
6778
|
-
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").option("--new", "Create a new account (skip the choice prompt)").option("-n, --name <name>", "Full name (for account creation)").action(async (options) => {
|
|
6841
|
+
program.command("setup").description("Smart setup wizard for Harmony MCP (recommended)").argument("[slug]", "Project slug — resolves to workspace + project in one step (e.g. harmony-6590761b)").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 (UUID)").option("-p, --project <id>", "Set project context (UUID)").option("--skip-context", "Skip workspace/project selection").option("--skip-docs", "Skip project docs scaffold/verification").option("--new", "Create a new account (skip the choice prompt)").option("-n, --name <name>", "Full name (for account creation)").action(async (slug, options) => {
|
|
6779
6842
|
await runSetup({
|
|
6780
6843
|
force: options.force,
|
|
6781
6844
|
apiKey: options.apiKey,
|
|
6782
6845
|
userEmail: options.email,
|
|
6783
6846
|
agents: options.agents,
|
|
6784
6847
|
installMode: options.global ? "global" : options.local ? "local" : undefined,
|
|
6848
|
+
projectSlug: slug,
|
|
6785
6849
|
workspaceId: options.workspace,
|
|
6786
6850
|
projectId: options.project,
|
|
6787
6851
|
skipContext: options.skipContext,
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -139,6 +139,10 @@ program
|
|
|
139
139
|
program
|
|
140
140
|
.command("setup")
|
|
141
141
|
.description("Smart setup wizard for Harmony MCP (recommended)")
|
|
142
|
+
.argument(
|
|
143
|
+
"[slug]",
|
|
144
|
+
"Project slug — resolves to workspace + project in one step (e.g. harmony-6590761b)",
|
|
145
|
+
)
|
|
142
146
|
.option("-f, --force", "Overwrite existing configuration files")
|
|
143
147
|
.option("-k, --api-key <key>", "API key (skips prompt)")
|
|
144
148
|
.option("-e, --email <email>", "Your email for auto-assignment")
|
|
@@ -148,13 +152,13 @@ program
|
|
|
148
152
|
)
|
|
149
153
|
.option("-l, --local", "Install skills locally in project directory")
|
|
150
154
|
.option("-g, --global", "Install skills globally (recommended)")
|
|
151
|
-
.option("-w, --workspace <id>", "Set workspace context")
|
|
152
|
-
.option("-p, --project <id>", "Set project context")
|
|
155
|
+
.option("-w, --workspace <id>", "Set workspace context (UUID)")
|
|
156
|
+
.option("-p, --project <id>", "Set project context (UUID)")
|
|
153
157
|
.option("--skip-context", "Skip workspace/project selection")
|
|
154
158
|
.option("--skip-docs", "Skip project docs scaffold/verification")
|
|
155
159
|
.option("--new", "Create a new account (skip the choice prompt)")
|
|
156
160
|
.option("-n, --name <name>", "Full name (for account creation)")
|
|
157
|
-
.action(async (options) => {
|
|
161
|
+
.action(async (slug, options) => {
|
|
158
162
|
await runSetup({
|
|
159
163
|
force: options.force,
|
|
160
164
|
apiKey: options.apiKey,
|
|
@@ -165,6 +169,7 @@ program
|
|
|
165
169
|
: options.local
|
|
166
170
|
? "local"
|
|
167
171
|
: undefined,
|
|
172
|
+
projectSlug: slug,
|
|
168
173
|
workspaceId: options.workspace,
|
|
169
174
|
projectId: options.project,
|
|
170
175
|
skipContext: options.skipContext,
|
package/src/tui/setup.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import {
|
|
2
3
|
existsSync,
|
|
3
4
|
lstatSync,
|
|
@@ -38,6 +39,7 @@ export interface SetupOptions {
|
|
|
38
39
|
installMode?: InstallMode;
|
|
39
40
|
workspaceId?: string;
|
|
40
41
|
projectId?: string;
|
|
42
|
+
projectSlug?: string;
|
|
41
43
|
skipContext?: boolean;
|
|
42
44
|
skipDocs?: boolean;
|
|
43
45
|
newAccount?: boolean;
|
|
@@ -211,11 +213,40 @@ async function fetchProjects(
|
|
|
211
213
|
return data.projects || [];
|
|
212
214
|
}
|
|
213
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Resolve a project slug to {workspaceId, projectId}. Used by
|
|
218
|
+
* `npx @gethmy/mcp setup <slug>` so users don't have to copy raw UUIDs.
|
|
219
|
+
*/
|
|
220
|
+
async function resolveProjectSlug(
|
|
221
|
+
apiKey: string,
|
|
222
|
+
slug: string,
|
|
223
|
+
): Promise<{ workspaceId: string; projectId: string } | null> {
|
|
224
|
+
const response = await fetch(
|
|
225
|
+
`${API_URL}/v1/projects/resolve/${encodeURIComponent(slug)}`,
|
|
226
|
+
{
|
|
227
|
+
method: "GET",
|
|
228
|
+
headers: {
|
|
229
|
+
"Content-Type": "application/json",
|
|
230
|
+
"X-API-Key": apiKey,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
if (response.status === 404) return null;
|
|
236
|
+
if (!response.ok) {
|
|
237
|
+
throw new Error(`Failed to resolve project slug: ${response.status}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const data = await response.json();
|
|
241
|
+
return { workspaceId: data.workspaceId, projectId: data.projectId };
|
|
242
|
+
}
|
|
243
|
+
|
|
214
244
|
export interface FileToWrite {
|
|
215
245
|
path: string;
|
|
216
246
|
content: string;
|
|
217
247
|
type: "text" | "json" | "toml";
|
|
218
248
|
tomlSection?: string;
|
|
249
|
+
mode?: number;
|
|
219
250
|
}
|
|
220
251
|
|
|
221
252
|
export interface SymlinkToCreate {
|
|
@@ -288,6 +319,36 @@ async function getAgentFiles(
|
|
|
288
319
|
);
|
|
289
320
|
}
|
|
290
321
|
|
|
322
|
+
// Pre-populate ~/.hmy/VERSION and ~/.hmy/bin/hmy-update-check so the
|
|
323
|
+
// lazy bootstrap inside the skill preamble is bypassed on first run.
|
|
324
|
+
// Without this, the bootstrap writes "1.0.0" as a fallback whenever the
|
|
325
|
+
// version fetch times out (edge function cold start) — triggering a
|
|
326
|
+
// spurious "upgrade to v6" prompt the moment the skill is first invoked.
|
|
327
|
+
try {
|
|
328
|
+
const updateCheckFetched = await client.fetchSkill("hmy-update-check");
|
|
329
|
+
const actualHash = createHash("sha256")
|
|
330
|
+
.update(updateCheckFetched.content)
|
|
331
|
+
.digest("hex");
|
|
332
|
+
if (actualHash !== updateCheckFetched.sha256) {
|
|
333
|
+
throw new Error(
|
|
334
|
+
`hmy-update-check integrity check failed: expected ${updateCheckFetched.sha256}, got ${actualHash}`,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
files.push({
|
|
338
|
+
path: join(home, ".hmy", "bin", "hmy-update-check"),
|
|
339
|
+
content: updateCheckFetched.content,
|
|
340
|
+
type: "text",
|
|
341
|
+
mode: 0o755,
|
|
342
|
+
});
|
|
343
|
+
files.push({
|
|
344
|
+
path: join(home, ".hmy", "VERSION"),
|
|
345
|
+
content: versionInfo.version,
|
|
346
|
+
type: "text",
|
|
347
|
+
});
|
|
348
|
+
} catch {
|
|
349
|
+
// Non-fatal — bootstrap will install both on first skill invocation.
|
|
350
|
+
}
|
|
351
|
+
|
|
291
352
|
// Note: MCP server registration is handled separately via `claude mcp add` CLI
|
|
292
353
|
// in runSetup() after file writing, with fallback to settings.json if CLI unavailable
|
|
293
354
|
break;
|
|
@@ -524,8 +585,8 @@ export async function runSetup(options: SetupOptions = {}): Promise<void> {
|
|
|
524
585
|
let needsSkills = !skillsStatus.installed || options.force;
|
|
525
586
|
let needsContext = !hasContext && !options.skipContext;
|
|
526
587
|
|
|
527
|
-
// If workspace/project provided via flags, we'll set context
|
|
528
|
-
if (options.workspaceId || options.projectId) {
|
|
588
|
+
// If workspace/project/slug provided via flags or argument, we'll set context
|
|
589
|
+
if (options.workspaceId || options.projectId || options.projectSlug) {
|
|
529
590
|
needsContext = true;
|
|
530
591
|
}
|
|
531
592
|
|
|
@@ -806,6 +867,35 @@ export async function runSetup(options: SetupOptions = {}): Promise<void> {
|
|
|
806
867
|
selectedWorkspaceNameFromSignup;
|
|
807
868
|
let selectedProjectName: string | undefined = selectedProjectNameFromSignup;
|
|
808
869
|
|
|
870
|
+
// Resolve project slug shorthand (e.g. `npx @gethmy/mcp setup harmony-6590761b`).
|
|
871
|
+
// Slug wins over --workspace/--project flags only when those aren't already set
|
|
872
|
+
// from signup or explicit flags.
|
|
873
|
+
if (
|
|
874
|
+
options.projectSlug &&
|
|
875
|
+
apiKey &&
|
|
876
|
+
(!selectedWorkspaceId || !selectedProjectId)
|
|
877
|
+
) {
|
|
878
|
+
spinner.start(`Resolving project slug "${options.projectSlug}"...`);
|
|
879
|
+
try {
|
|
880
|
+
const resolved = await resolveProjectSlug(apiKey, options.projectSlug);
|
|
881
|
+
if (resolved) {
|
|
882
|
+
selectedWorkspaceId = selectedWorkspaceId || resolved.workspaceId;
|
|
883
|
+
selectedProjectId = selectedProjectId || resolved.projectId;
|
|
884
|
+
spinner.stop(colors.success(`Resolved "${options.projectSlug}"`));
|
|
885
|
+
} else {
|
|
886
|
+
spinner.stop(
|
|
887
|
+
colors.warning(`No project found for slug "${options.projectSlug}"`),
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
} catch (error) {
|
|
891
|
+
spinner.stop(
|
|
892
|
+
colors.warning(
|
|
893
|
+
`Could not resolve slug: ${error instanceof Error ? error.message : "unknown error"}`,
|
|
894
|
+
),
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
809
899
|
// Skip context selection if we just created a new account
|
|
810
900
|
if (createdNewAccount) {
|
|
811
901
|
needsContext = false;
|
package/src/tui/writer.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
chmodSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
2
8
|
import { homedir } from "node:os";
|
|
3
9
|
import { dirname } from "node:path";
|
|
4
10
|
import * as p from "@clack/prompts";
|
|
@@ -15,6 +21,7 @@ export interface FileResult {
|
|
|
15
21
|
interface WriteOptions {
|
|
16
22
|
force?: boolean;
|
|
17
23
|
merge?: boolean;
|
|
24
|
+
mode?: number;
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
/**
|
|
@@ -42,9 +49,12 @@ export function writeFile(
|
|
|
42
49
|
|
|
43
50
|
try {
|
|
44
51
|
ensureDir(dirname(filePath));
|
|
45
|
-
|
|
46
|
-
const mode =
|
|
52
|
+
const defaultMode = filePath.includes(".harmony-mcp") ? 0o600 : 0o644;
|
|
53
|
+
const mode = options.mode ?? defaultMode;
|
|
47
54
|
writeFileSync(filePath, content, { mode });
|
|
55
|
+
if (options.mode !== undefined) {
|
|
56
|
+
chmodSync(filePath, options.mode);
|
|
57
|
+
}
|
|
48
58
|
return { path: filePath, action: exists ? "update" : "create" };
|
|
49
59
|
} catch (error) {
|
|
50
60
|
return {
|
|
@@ -183,6 +193,7 @@ export async function writeFilesWithProgress(
|
|
|
183
193
|
type: "text" | "json" | "toml";
|
|
184
194
|
jsonKey?: string;
|
|
185
195
|
tomlSection?: string;
|
|
196
|
+
mode?: number;
|
|
186
197
|
}>,
|
|
187
198
|
options: WriteOptions = {},
|
|
188
199
|
): Promise<FileResult[]> {
|
|
@@ -201,7 +212,10 @@ export async function writeFilesWithProgress(
|
|
|
201
212
|
} else if (file.type === "toml" && file.tomlSection) {
|
|
202
213
|
result = appendToToml(file.path, file.tomlSection, file.content, options);
|
|
203
214
|
} else {
|
|
204
|
-
result = writeFile(file.path, file.content,
|
|
215
|
+
result = writeFile(file.path, file.content, {
|
|
216
|
+
...options,
|
|
217
|
+
mode: file.mode,
|
|
218
|
+
});
|
|
205
219
|
}
|
|
206
220
|
|
|
207
221
|
results.push(result);
|