@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/src/tui/setup.ts CHANGED
@@ -15,8 +15,12 @@ import {
15
15
  hasProjectContext,
16
16
  isConfigured,
17
17
  loadConfig,
18
+ saveConfig,
18
19
  saveLocalConfig,
20
+ setActiveProject,
21
+ setActiveWorkspace,
19
22
  } from "../config.js";
23
+ import { onboardNewUser } from "../onboard.js";
20
24
  import { buildSkillFile, HARMONY_WORKFLOW_PROMPT } from "../skills.js";
21
25
  import { type AgentId, detectAgents } from "./agents.js";
22
26
  import { runDocsStep } from "./docs.js";
@@ -35,6 +39,8 @@ export interface SetupOptions {
35
39
  projectId?: string;
36
40
  skipContext?: boolean;
37
41
  skipDocs?: boolean;
42
+ newAccount?: boolean;
43
+ name?: string;
38
44
  }
39
45
 
40
46
  // Central skills directory for global installation
@@ -499,55 +505,191 @@ export async function runSetup(options: SetupOptions = {}): Promise<void> {
499
505
  needsContext = true;
500
506
  }
501
507
 
502
- // Step 1: API Key
508
+ // Step 1: API Key (or create account)
503
509
  let apiKey = options.apiKey || existingConfig.apiKey;
504
510
  let userEmail: string | undefined =
505
511
  options.userEmail || existingConfig.userEmail || undefined;
512
+ let selectedWorkspaceIdFromSignup: string | undefined;
513
+ let selectedProjectIdFromSignup: string | undefined;
514
+ let selectedWorkspaceNameFromSignup: string | undefined;
515
+ let selectedProjectNameFromSignup: string | undefined;
516
+ let createdNewAccount = false;
506
517
 
507
518
  if (needsApiKey || !apiKey || !apiKey.startsWith("hmy_")) {
508
- const keyInput = await p.text({
509
- message: "Enter your Harmony API key",
510
- placeholder: "hmy_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
511
- validate: (value) => {
512
- if (!value) return "API key is required";
513
- if (!value.startsWith("hmy_")) return 'API key must start with "hmy_"';
514
- if (value.length < 20) return "API key is too short";
515
- return undefined;
516
- },
517
- });
519
+ // Determine path: create account or enter API key
520
+ let useNewAccount = options.newAccount === true;
521
+
522
+ if (!useNewAccount && options.apiKey) {
523
+ // API key passed via flag — skip the choice prompt
524
+ useNewAccount = false;
525
+ } else if (!useNewAccount && !options.apiKey) {
526
+ const getStarted = await p.select({
527
+ message: "How would you like to get started?",
528
+ options: [
529
+ {
530
+ value: "create",
531
+ label: "Create a free account",
532
+ hint: "recommended for new users",
533
+ },
534
+ {
535
+ value: "apikey",
536
+ label: "I already have an API key",
537
+ },
538
+ ],
539
+ });
518
540
 
519
- if (p.isCancel(keyInput)) {
520
- p.cancel("Setup cancelled");
521
- process.exit(0);
541
+ if (p.isCancel(getStarted)) {
542
+ p.cancel("Setup cancelled");
543
+ process.exit(0);
544
+ }
545
+
546
+ useNewAccount = getStarted === "create";
522
547
  }
523
548
 
524
- apiKey = keyInput as string;
525
- needsApiKey = true;
549
+ if (useNewAccount) {
550
+ // --- Create account flow ---
551
+ const fullName =
552
+ options.name ||
553
+ ((await p.text({
554
+ message: "Full name",
555
+ placeholder: "Jane Smith",
556
+ validate: (v) => {
557
+ if (!v || v.trim().length === 0) return "Name is required";
558
+ if (v.length > 100) return "Name must be 100 characters or less";
559
+ return undefined;
560
+ },
561
+ })) as string);
562
+
563
+ if (p.isCancel(fullName)) {
564
+ p.cancel("Setup cancelled");
565
+ process.exit(0);
566
+ }
567
+
568
+ const email =
569
+ options.userEmail ||
570
+ ((await p.text({
571
+ message: "Email",
572
+ placeholder: "you@example.com",
573
+ validate: (v) => {
574
+ if (!v) return "Email is required";
575
+ if (v.length > 254) return "Email is too long";
576
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v))
577
+ return "Invalid email format";
578
+ return undefined;
579
+ },
580
+ })) as string);
581
+
582
+ if (p.isCancel(email)) {
583
+ p.cancel("Setup cancelled");
584
+ process.exit(0);
585
+ }
586
+
587
+ const password = (await p.password({
588
+ message: "Password",
589
+ validate: (v) => {
590
+ if (!v) return "Password is required";
591
+ if (v.length < 8) return "Password must be at least 8 characters";
592
+ if (v.length > 128) return "Password must be 128 characters or less";
593
+ return undefined;
594
+ },
595
+ })) as string;
596
+
597
+ if (p.isCancel(password)) {
598
+ p.cancel("Setup cancelled");
599
+ process.exit(0);
600
+ }
601
+
602
+ const spinner = p.spinner();
603
+ spinner.start("Creating your account...");
604
+
605
+ try {
606
+ const result = await onboardNewUser({
607
+ email: email as string,
608
+ password: password as string,
609
+ fullName: fullName as string,
610
+ });
611
+
612
+ spinner.stop(
613
+ colors.success(`Account created for ${result.user.email}`),
614
+ );
615
+
616
+ apiKey = result.apiKey.rawKey;
617
+ userEmail = result.user.email;
618
+ selectedWorkspaceIdFromSignup = result.workspace.id;
619
+ selectedProjectIdFromSignup = result.project.id;
620
+ selectedWorkspaceNameFromSignup = result.workspace.name;
621
+ selectedProjectNameFromSignup = result.project.name;
622
+ createdNewAccount = true;
623
+ needsApiKey = true;
624
+
625
+ // Save config immediately
626
+ saveConfig({ apiKey, userEmail, apiUrl: API_URL });
627
+ setActiveWorkspace(selectedWorkspaceIdFromSignup);
628
+ setActiveProject(selectedProjectIdFromSignup);
629
+
630
+ p.log.success("Workspace and board created");
631
+ } catch (error) {
632
+ spinner.stop(colors.error("Account creation failed"));
633
+ const msg = error instanceof Error ? error.message : "Unknown error";
634
+ if (msg.includes("already") || msg.includes("409")) {
635
+ p.log.error(
636
+ "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'.",
637
+ );
638
+ } else {
639
+ p.log.error(msg);
640
+ p.log.info("Please try again or visit https://app.gethmy.com");
641
+ }
642
+ process.exit(1);
643
+ }
644
+ } else {
645
+ // --- Existing API key flow ---
646
+ const keyInput = await p.text({
647
+ message: "Enter your Harmony API key",
648
+ placeholder: "hmy_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
649
+ validate: (value) => {
650
+ if (!value) return "API key is required";
651
+ if (!value.startsWith("hmy_"))
652
+ return 'API key must start with "hmy_"';
653
+ if (value.length < 20) return "API key is too short";
654
+ return undefined;
655
+ },
656
+ });
657
+
658
+ if (p.isCancel(keyInput)) {
659
+ p.cancel("Setup cancelled");
660
+ process.exit(0);
661
+ }
662
+
663
+ apiKey = keyInput as string;
664
+ needsApiKey = true;
665
+ }
526
666
  } else {
527
667
  p.log.success(`Using existing API key: ${apiKey.slice(0, 8)}...`);
528
668
  }
529
669
 
530
- // Validate API key
670
+ // Validate API key (skip if we just created account — key is guaranteed valid)
531
671
  const spinner = p.spinner();
532
- spinner.start("Validating API key...");
672
+ if (!createdNewAccount) {
673
+ spinner.start("Validating API key...");
533
674
 
534
- const validation = await validateApiKey(apiKey);
675
+ const validation = await validateApiKey(apiKey);
535
676
 
536
- if (!validation.valid) {
537
- spinner.stop(colors.error("API key validation failed"));
538
- p.log.error(validation.error || "Could not connect to Harmony API");
539
- p.log.info("Get an API key at: https://app.gethmy.com/user/keys");
540
- process.exit(1);
541
- }
677
+ if (!validation.valid) {
678
+ spinner.stop(colors.error("API key validation failed"));
679
+ p.log.error(validation.error || "Could not connect to Harmony API");
680
+ p.log.info("Get an API key at: https://app.gethmy.com/user/keys");
681
+ process.exit(1);
682
+ }
542
683
 
543
- if (!userEmail) {
544
- userEmail = validation.email;
684
+ if (!userEmail) {
685
+ userEmail = validation.email;
686
+ }
687
+ spinner.stop(
688
+ colors.success(
689
+ userEmail ? `Connected as ${userEmail}` : "API key validated",
690
+ ),
691
+ );
545
692
  }
546
- spinner.stop(
547
- colors.success(
548
- userEmail ? `Connected as ${userEmail}` : "API key validated",
549
- ),
550
- );
551
693
 
552
694
  // Step 2: Check if skills are already installed
553
695
  let selectedAgents: AgentId[] = [];
@@ -633,10 +775,17 @@ export async function runSetup(options: SetupOptions = {}): Promise<void> {
633
775
  }
634
776
 
635
777
  // Step 3: Workspace and Project Selection
636
- let selectedWorkspaceId = options.workspaceId;
637
- let selectedProjectId = options.projectId;
638
- let selectedWorkspaceName: string | undefined;
639
- let selectedProjectName: string | undefined;
778
+ let selectedWorkspaceId =
779
+ selectedWorkspaceIdFromSignup || options.workspaceId;
780
+ let selectedProjectId = selectedProjectIdFromSignup || options.projectId;
781
+ let selectedWorkspaceName: string | undefined =
782
+ selectedWorkspaceNameFromSignup;
783
+ let selectedProjectName: string | undefined = selectedProjectNameFromSignup;
784
+
785
+ // Skip context selection if we just created a new account
786
+ if (createdNewAccount) {
787
+ needsContext = false;
788
+ }
640
789
 
641
790
  if (needsContext && !options.skipContext) {
642
791
  // Fetch workspaces
@@ -935,46 +1084,79 @@ export async function runSetup(options: SetupOptions = {}): Promise<void> {
935
1084
  console.log("");
936
1085
  p.outro(colors.success("Setup complete!"));
937
1086
 
938
- console.log("");
939
- console.log(` ${colors.bold("Configuration:")}`);
940
- console.log(` API key: ${formatPath(getConfigPath(), home)}`);
941
- if (needsSkills && selectedAgents.length > 0) {
1087
+ if (createdNewAccount && selectedWorkspaceNameFromSignup) {
1088
+ // New account: show board URL and next steps
1089
+ const wsSlug = selectedWorkspaceNameFromSignup
1090
+ .toLowerCase()
1091
+ .replace(/[^a-z0-9]+/g, "-")
1092
+ .replace(/(^-|-$)/g, "");
1093
+ const projSlug = (selectedProjectNameFromSignup || "my-first-board")
1094
+ .toLowerCase()
1095
+ .replace(/[^a-z0-9]+/g, "-")
1096
+ .replace(/(^-|-$)/g, "");
1097
+
1098
+ console.log("");
942
1099
  console.log(
943
- ` Skills: ${installMode === "global" ? "~/.agents/skills/ (global)" : ".claude/skills/ (local)"}`,
1100
+ ` ${colors.bold("Your board:")} ${colors.highlight(`https://app.gethmy.com/${wsSlug}/${projSlug}`)}`,
944
1101
  );
945
- }
946
- if (selectedWorkspaceId || selectedProjectId) {
947
- console.log(` Context: ${formatPath(getLocalConfigPath(cwd), home)}`);
948
- }
949
-
950
- console.log("");
951
- console.log(` ${colors.bold("Usage:")}`);
952
-
953
- if (!needsSkills || selectedAgents.includes("claude")) {
1102
+ console.log("");
1103
+ console.log(` ${colors.bold("Next steps:")}`);
954
1104
  console.log(
955
- ` ${colors.brand("Claude Code:")} ${colors.highlight("/hmy #42")} or ${colors.highlight("/hmy-plan")} ${colors.dim("(create or execute plans)")}`,
1105
+ ` 1. Open Claude Code and say: ${colors.highlight('"Show me my board"')}`,
956
1106
  );
957
- }
958
-
959
- if (selectedAgents.includes("codex")) {
960
1107
  console.log(
961
- ` ${colors.brand("Codex:")} ${colors.highlight("/prompts:hmy #42")}`,
1108
+ ` 2. Create a card: ${colors.highlight('"Create a card called Auth token refresh"')}`,
962
1109
  );
963
- }
1110
+ console.log(
1111
+ ` 3. Start the daemon: ${colors.highlight("npx @gethmy/agent")}`,
1112
+ );
1113
+ console.log("");
1114
+ console.log(` ${colors.dim("Happy shipping!")}`);
1115
+ } else {
1116
+ // Existing user: show config paths and usage
1117
+ console.log("");
1118
+ console.log(` ${colors.bold("Configuration:")}`);
1119
+ console.log(` API key: ${formatPath(getConfigPath(), home)}`);
1120
+ if (needsSkills && selectedAgents.length > 0) {
1121
+ console.log(
1122
+ ` Skills: ${installMode === "global" ? "~/.agents/skills/ (global)" : ".claude/skills/ (local)"}`,
1123
+ );
1124
+ }
1125
+ if (selectedWorkspaceId || selectedProjectId) {
1126
+ console.log(
1127
+ ` Context: ${formatPath(getLocalConfigPath(cwd), home)}`,
1128
+ );
1129
+ }
1130
+
1131
+ console.log("");
1132
+ console.log(` ${colors.bold("Usage:")}`);
1133
+
1134
+ if (!needsSkills || selectedAgents.includes("claude")) {
1135
+ console.log(
1136
+ ` ${colors.brand("Claude Code:")} ${colors.highlight("/hmy #42")} or ${colors.highlight("/hmy-plan")} ${colors.dim("(create or execute plans)")}`,
1137
+ );
1138
+ }
964
1139
 
965
- if (
966
- selectedAgents.includes("cursor") ||
967
- selectedAgents.includes("windsurf")
968
- ) {
1140
+ if (selectedAgents.includes("codex")) {
1141
+ console.log(
1142
+ ` ${colors.brand("Codex:")} ${colors.highlight("/prompts:hmy #42")}`,
1143
+ );
1144
+ }
1145
+
1146
+ if (
1147
+ selectedAgents.includes("cursor") ||
1148
+ selectedAgents.includes("windsurf")
1149
+ ) {
1150
+ console.log(
1151
+ ` ${colors.brand("Cursor:")} MCP tools available automatically`,
1152
+ );
1153
+ }
1154
+
1155
+ console.log("");
1156
+ console.log(` ${colors.dim("Add to new project: npx @gethmy/mcp setup")}`);
969
1157
  console.log(
970
- ` ${colors.brand("Cursor/Windsurf:")} MCP tools available automatically`,
1158
+ ` ${colors.dim("Need help? Visit https://app.gethmy.com/docs/mcp")}`,
971
1159
  );
972
1160
  }
973
-
974
- console.log("");
975
- console.log(` ${colors.dim("Add to new project: npx @gethmy/mcp setup")}`);
976
- console.log(
977
- ` ${colors.dim("Need help? Visit https://app.gethmy.com/docs/mcp")}`,
978
- );
979
1161
  console.log("");
980
1162
  }