@gethmy/mcp 2.2.0 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -7
- package/dist/cli.js +2515 -2134
- package/dist/http.js +6 -4
- package/dist/index.js +8088 -7890
- package/dist/lib/__tests__/auto-session.test.js +33 -33
- package/dist/lib/api-client.js +116 -0
- package/dist/lib/auto-session.js +41 -5
- package/dist/lib/cli.js +9 -0
- package/dist/lib/onboard.js +36 -0
- package/dist/lib/server.js +150 -169
- package/dist/lib/skills.js +1 -1
- package/dist/lib/tui/setup.js +212 -59
- package/dist/remote.js +7132 -10614
- package/package.json +2 -1
- package/src/__tests__/auto-session.test.ts +33 -33
- package/src/api-client.ts +205 -0
- package/src/auto-session.ts +54 -4
- package/src/cli.ts +9 -0
- package/src/onboard.ts +93 -0
- package/src/remote.ts +2 -1
- package/src/server.ts +178 -221
- package/src/skills.ts +1 -1
- package/src/tui/setup.ts +249 -67
package/dist/lib/tui/setup.js
CHANGED
|
@@ -2,7 +2,8 @@ import { existsSync, lstatSync, mkdirSync, symlinkSync, unlinkSync, } from "node
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
|
-
import { areSkillsInstalled, getConfigPath, getLocalConfigPath, hasProjectContext, isConfigured, loadConfig, saveLocalConfig, } from "../config.js";
|
|
5
|
+
import { areSkillsInstalled, getConfigPath, getLocalConfigPath, hasProjectContext, isConfigured, loadConfig, saveConfig, saveLocalConfig, setActiveProject, setActiveWorkspace, } from "../config.js";
|
|
6
|
+
import { onboardNewUser } from "../onboard.js";
|
|
6
7
|
import { buildSkillFile, HARMONY_WORKFLOW_PROMPT } from "../skills.js";
|
|
7
8
|
import { detectAgents } from "./agents.js";
|
|
8
9
|
import { runDocsStep } from "./docs.js";
|
|
@@ -388,47 +389,172 @@ export async function runSetup(options = {}) {
|
|
|
388
389
|
if (options.workspaceId || options.projectId) {
|
|
389
390
|
needsContext = true;
|
|
390
391
|
}
|
|
391
|
-
// Step 1: API Key
|
|
392
|
+
// Step 1: API Key (or create account)
|
|
392
393
|
let apiKey = options.apiKey || existingConfig.apiKey;
|
|
393
394
|
let userEmail = options.userEmail || existingConfig.userEmail || undefined;
|
|
395
|
+
let selectedWorkspaceIdFromSignup;
|
|
396
|
+
let selectedProjectIdFromSignup;
|
|
397
|
+
let selectedWorkspaceNameFromSignup;
|
|
398
|
+
let selectedProjectNameFromSignup;
|
|
399
|
+
let createdNewAccount = false;
|
|
394
400
|
if (needsApiKey || !apiKey || !apiKey.startsWith("hmy_")) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
401
|
+
// Determine path: create account or enter API key
|
|
402
|
+
let useNewAccount = options.newAccount === true;
|
|
403
|
+
if (!useNewAccount && options.apiKey) {
|
|
404
|
+
// API key passed via flag — skip the choice prompt
|
|
405
|
+
useNewAccount = false;
|
|
406
|
+
}
|
|
407
|
+
else if (!useNewAccount && !options.apiKey) {
|
|
408
|
+
const getStarted = await p.select({
|
|
409
|
+
message: "How would you like to get started?",
|
|
410
|
+
options: [
|
|
411
|
+
{
|
|
412
|
+
value: "create",
|
|
413
|
+
label: "Create a free account",
|
|
414
|
+
hint: "recommended for new users",
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
value: "apikey",
|
|
418
|
+
label: "I already have an API key",
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
});
|
|
422
|
+
if (p.isCancel(getStarted)) {
|
|
423
|
+
p.cancel("Setup cancelled");
|
|
424
|
+
process.exit(0);
|
|
425
|
+
}
|
|
426
|
+
useNewAccount = getStarted === "create";
|
|
427
|
+
}
|
|
428
|
+
if (useNewAccount) {
|
|
429
|
+
// --- Create account flow ---
|
|
430
|
+
const fullName = options.name ||
|
|
431
|
+
(await p.text({
|
|
432
|
+
message: "Full name",
|
|
433
|
+
placeholder: "Jane Smith",
|
|
434
|
+
validate: (v) => {
|
|
435
|
+
if (!v || v.trim().length === 0)
|
|
436
|
+
return "Name is required";
|
|
437
|
+
if (v.length > 100)
|
|
438
|
+
return "Name must be 100 characters or less";
|
|
439
|
+
return undefined;
|
|
440
|
+
},
|
|
441
|
+
}));
|
|
442
|
+
if (p.isCancel(fullName)) {
|
|
443
|
+
p.cancel("Setup cancelled");
|
|
444
|
+
process.exit(0);
|
|
445
|
+
}
|
|
446
|
+
const email = options.userEmail ||
|
|
447
|
+
(await p.text({
|
|
448
|
+
message: "Email",
|
|
449
|
+
placeholder: "you@example.com",
|
|
450
|
+
validate: (v) => {
|
|
451
|
+
if (!v)
|
|
452
|
+
return "Email is required";
|
|
453
|
+
if (v.length > 254)
|
|
454
|
+
return "Email is too long";
|
|
455
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v))
|
|
456
|
+
return "Invalid email format";
|
|
457
|
+
return undefined;
|
|
458
|
+
},
|
|
459
|
+
}));
|
|
460
|
+
if (p.isCancel(email)) {
|
|
461
|
+
p.cancel("Setup cancelled");
|
|
462
|
+
process.exit(0);
|
|
463
|
+
}
|
|
464
|
+
const password = (await p.password({
|
|
465
|
+
message: "Password",
|
|
466
|
+
validate: (v) => {
|
|
467
|
+
if (!v)
|
|
468
|
+
return "Password is required";
|
|
469
|
+
if (v.length < 8)
|
|
470
|
+
return "Password must be at least 8 characters";
|
|
471
|
+
if (v.length > 128)
|
|
472
|
+
return "Password must be 128 characters or less";
|
|
473
|
+
return undefined;
|
|
474
|
+
},
|
|
475
|
+
}));
|
|
476
|
+
if (p.isCancel(password)) {
|
|
477
|
+
p.cancel("Setup cancelled");
|
|
478
|
+
process.exit(0);
|
|
479
|
+
}
|
|
480
|
+
const spinner = p.spinner();
|
|
481
|
+
spinner.start("Creating your account...");
|
|
482
|
+
try {
|
|
483
|
+
const result = await onboardNewUser({
|
|
484
|
+
email: email,
|
|
485
|
+
password: password,
|
|
486
|
+
fullName: fullName,
|
|
487
|
+
});
|
|
488
|
+
spinner.stop(colors.success(`Account created for ${result.user.email}`));
|
|
489
|
+
apiKey = result.apiKey.rawKey;
|
|
490
|
+
userEmail = result.user.email;
|
|
491
|
+
selectedWorkspaceIdFromSignup = result.workspace.id;
|
|
492
|
+
selectedProjectIdFromSignup = result.project.id;
|
|
493
|
+
selectedWorkspaceNameFromSignup = result.workspace.name;
|
|
494
|
+
selectedProjectNameFromSignup = result.project.name;
|
|
495
|
+
createdNewAccount = true;
|
|
496
|
+
needsApiKey = true;
|
|
497
|
+
// Save config immediately
|
|
498
|
+
saveConfig({ apiKey, userEmail, apiUrl: API_URL });
|
|
499
|
+
setActiveWorkspace(selectedWorkspaceIdFromSignup);
|
|
500
|
+
setActiveProject(selectedProjectIdFromSignup);
|
|
501
|
+
p.log.success("Workspace and board created");
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
spinner.stop(colors.error("Account creation failed"));
|
|
505
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
506
|
+
if (msg.includes("already") || msg.includes("409")) {
|
|
507
|
+
p.log.error("Account already exists. Sign in at app.gethmy.com to get your API key, or re-run setup and choose 'I already have an API key'.");
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
p.log.error(msg);
|
|
511
|
+
p.log.info("Please try again or visit https://app.gethmy.com");
|
|
512
|
+
}
|
|
513
|
+
process.exit(1);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
// --- Existing API key flow ---
|
|
518
|
+
const keyInput = await p.text({
|
|
519
|
+
message: "Enter your Harmony API key",
|
|
520
|
+
placeholder: "hmy_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
521
|
+
validate: (value) => {
|
|
522
|
+
if (!value)
|
|
523
|
+
return "API key is required";
|
|
524
|
+
if (!value.startsWith("hmy_"))
|
|
525
|
+
return 'API key must start with "hmy_"';
|
|
526
|
+
if (value.length < 20)
|
|
527
|
+
return "API key is too short";
|
|
528
|
+
return undefined;
|
|
529
|
+
},
|
|
530
|
+
});
|
|
531
|
+
if (p.isCancel(keyInput)) {
|
|
532
|
+
p.cancel("Setup cancelled");
|
|
533
|
+
process.exit(0);
|
|
534
|
+
}
|
|
535
|
+
apiKey = keyInput;
|
|
536
|
+
needsApiKey = true;
|
|
411
537
|
}
|
|
412
|
-
apiKey = keyInput;
|
|
413
|
-
needsApiKey = true;
|
|
414
538
|
}
|
|
415
539
|
else {
|
|
416
540
|
p.log.success(`Using existing API key: ${apiKey.slice(0, 8)}...`);
|
|
417
541
|
}
|
|
418
|
-
// Validate API key
|
|
542
|
+
// Validate API key (skip if we just created account — key is guaranteed valid)
|
|
419
543
|
const spinner = p.spinner();
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
userEmail
|
|
430
|
-
|
|
431
|
-
|
|
544
|
+
if (!createdNewAccount) {
|
|
545
|
+
spinner.start("Validating API key...");
|
|
546
|
+
const validation = await validateApiKey(apiKey);
|
|
547
|
+
if (!validation.valid) {
|
|
548
|
+
spinner.stop(colors.error("API key validation failed"));
|
|
549
|
+
p.log.error(validation.error || "Could not connect to Harmony API");
|
|
550
|
+
p.log.info("Get an API key at: https://app.gethmy.com/user/keys");
|
|
551
|
+
process.exit(1);
|
|
552
|
+
}
|
|
553
|
+
if (!userEmail) {
|
|
554
|
+
userEmail = validation.email;
|
|
555
|
+
}
|
|
556
|
+
spinner.stop(colors.success(userEmail ? `Connected as ${userEmail}` : "API key validated"));
|
|
557
|
+
}
|
|
432
558
|
// Step 2: Check if skills are already installed
|
|
433
559
|
let selectedAgents = [];
|
|
434
560
|
let installMode = options.installMode || "global";
|
|
@@ -502,10 +628,14 @@ export async function runSetup(options = {}) {
|
|
|
502
628
|
}
|
|
503
629
|
}
|
|
504
630
|
// Step 3: Workspace and Project Selection
|
|
505
|
-
let selectedWorkspaceId = options.workspaceId;
|
|
506
|
-
let selectedProjectId = options.projectId;
|
|
507
|
-
let selectedWorkspaceName;
|
|
508
|
-
let selectedProjectName;
|
|
631
|
+
let selectedWorkspaceId = selectedWorkspaceIdFromSignup || options.workspaceId;
|
|
632
|
+
let selectedProjectId = selectedProjectIdFromSignup || options.projectId;
|
|
633
|
+
let selectedWorkspaceName = selectedWorkspaceNameFromSignup;
|
|
634
|
+
let selectedProjectName = selectedProjectNameFromSignup;
|
|
635
|
+
// Skip context selection if we just created a new account
|
|
636
|
+
if (createdNewAccount) {
|
|
637
|
+
needsContext = false;
|
|
638
|
+
}
|
|
509
639
|
if (needsContext && !options.skipContext) {
|
|
510
640
|
// Fetch workspaces
|
|
511
641
|
spinner.start("Fetching workspaces...");
|
|
@@ -742,29 +872,52 @@ export async function runSetup(options = {}) {
|
|
|
742
872
|
// Step 10: Show completion message
|
|
743
873
|
console.log("");
|
|
744
874
|
p.outro(colors.success("Setup complete!"));
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
console.log(
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
console.log(`
|
|
875
|
+
if (createdNewAccount && selectedWorkspaceNameFromSignup) {
|
|
876
|
+
// New account: show board URL and next steps
|
|
877
|
+
const wsSlug = selectedWorkspaceNameFromSignup
|
|
878
|
+
.toLowerCase()
|
|
879
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
880
|
+
.replace(/(^-|-$)/g, "");
|
|
881
|
+
const projSlug = (selectedProjectNameFromSignup || "my-first-board")
|
|
882
|
+
.toLowerCase()
|
|
883
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
884
|
+
.replace(/(^-|-$)/g, "");
|
|
885
|
+
console.log("");
|
|
886
|
+
console.log(` ${colors.bold("Your board:")} ${colors.highlight(`https://app.gethmy.com/${wsSlug}/${projSlug}`)}`);
|
|
887
|
+
console.log("");
|
|
888
|
+
console.log(` ${colors.bold("Next steps:")}`);
|
|
889
|
+
console.log(` 1. Open Claude Code and say: ${colors.highlight('"Show me my board"')}`);
|
|
890
|
+
console.log(` 2. Create a card: ${colors.highlight('"Create a card called Auth token refresh"')}`);
|
|
891
|
+
console.log(` 3. Start the daemon: ${colors.highlight("npx @gethmy/agent")}`);
|
|
892
|
+
console.log("");
|
|
893
|
+
console.log(` ${colors.dim("Happy shipping!")}`);
|
|
761
894
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
console.log(
|
|
895
|
+
else {
|
|
896
|
+
// Existing user: show config paths and usage
|
|
897
|
+
console.log("");
|
|
898
|
+
console.log(` ${colors.bold("Configuration:")}`);
|
|
899
|
+
console.log(` API key: ${formatPath(getConfigPath(), home)}`);
|
|
900
|
+
if (needsSkills && selectedAgents.length > 0) {
|
|
901
|
+
console.log(` Skills: ${installMode === "global" ? "~/.agents/skills/ (global)" : ".claude/skills/ (local)"}`);
|
|
902
|
+
}
|
|
903
|
+
if (selectedWorkspaceId || selectedProjectId) {
|
|
904
|
+
console.log(` Context: ${formatPath(getLocalConfigPath(cwd), home)}`);
|
|
905
|
+
}
|
|
906
|
+
console.log("");
|
|
907
|
+
console.log(` ${colors.bold("Usage:")}`);
|
|
908
|
+
if (!needsSkills || selectedAgents.includes("claude")) {
|
|
909
|
+
console.log(` ${colors.brand("Claude Code:")} ${colors.highlight("/hmy #42")} or ${colors.highlight("/hmy-plan")} ${colors.dim("(create or execute plans)")}`);
|
|
910
|
+
}
|
|
911
|
+
if (selectedAgents.includes("codex")) {
|
|
912
|
+
console.log(` ${colors.brand("Codex:")} ${colors.highlight("/prompts:hmy #42")}`);
|
|
913
|
+
}
|
|
914
|
+
if (selectedAgents.includes("cursor") ||
|
|
915
|
+
selectedAgents.includes("windsurf")) {
|
|
916
|
+
console.log(` ${colors.brand("Cursor:")} MCP tools available automatically`);
|
|
917
|
+
}
|
|
918
|
+
console.log("");
|
|
919
|
+
console.log(` ${colors.dim("Add to new project: npx @gethmy/mcp setup")}`);
|
|
920
|
+
console.log(` ${colors.dim("Need help? Visit https://app.gethmy.com/docs/mcp")}`);
|
|
765
921
|
}
|
|
766
922
|
console.log("");
|
|
767
|
-
console.log(` ${colors.dim("Add to new project: npx @gethmy/mcp setup")}`);
|
|
768
|
-
console.log(` ${colors.dim("Need help? Visit https://app.gethmy.com/docs/mcp")}`);
|
|
769
|
-
console.log("");
|
|
770
923
|
}
|