@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.
@@ -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
- const keyInput = await p.text({
396
- message: "Enter your Harmony API key",
397
- placeholder: "hmy_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
398
- validate: (value) => {
399
- if (!value)
400
- return "API key is required";
401
- if (!value.startsWith("hmy_"))
402
- return 'API key must start with "hmy_"';
403
- if (value.length < 20)
404
- return "API key is too short";
405
- return undefined;
406
- },
407
- });
408
- if (p.isCancel(keyInput)) {
409
- p.cancel("Setup cancelled");
410
- process.exit(0);
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
- spinner.start("Validating API key...");
421
- const validation = await validateApiKey(apiKey);
422
- if (!validation.valid) {
423
- spinner.stop(colors.error("API key validation failed"));
424
- p.log.error(validation.error || "Could not connect to Harmony API");
425
- p.log.info("Get an API key at: https://app.gethmy.com/user/keys");
426
- process.exit(1);
427
- }
428
- if (!userEmail) {
429
- userEmail = validation.email;
430
- }
431
- spinner.stop(colors.success(userEmail ? `Connected as ${userEmail}` : "API key validated"));
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
- console.log("");
746
- console.log(` ${colors.bold("Configuration:")}`);
747
- console.log(` API key: ${formatPath(getConfigPath(), home)}`);
748
- if (needsSkills && selectedAgents.length > 0) {
749
- console.log(` Skills: ${installMode === "global" ? "~/.agents/skills/ (global)" : ".claude/skills/ (local)"}`);
750
- }
751
- if (selectedWorkspaceId || selectedProjectId) {
752
- console.log(` Context: ${formatPath(getLocalConfigPath(cwd), home)}`);
753
- }
754
- console.log("");
755
- console.log(` ${colors.bold("Usage:")}`);
756
- if (!needsSkills || selectedAgents.includes("claude")) {
757
- console.log(` ${colors.brand("Claude Code:")} ${colors.highlight("/hmy #42")} or ${colors.highlight("/hmy-plan")} ${colors.dim("(create or execute plans)")}`);
758
- }
759
- if (selectedAgents.includes("codex")) {
760
- console.log(` ${colors.brand("Codex:")} ${colors.highlight("/prompts:hmy #42")}`);
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
- if (selectedAgents.includes("cursor") ||
763
- selectedAgents.includes("windsurf")) {
764
- console.log(` ${colors.brand("Cursor/Windsurf:")} MCP tools available automatically`);
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
  }