@gethmy/mcp 2.1.3 → 2.2.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/src/server.ts CHANGED
@@ -59,13 +59,7 @@ import {
59
59
  } from "./context-assembly.js";
60
60
  import { autoExpandGraph } from "./graph-expansion.js";
61
61
  import { runLifecycleMaintenance } from "./lifecycle-maintenance.js";
62
- import {
63
- type CardData,
64
- type ColumnData,
65
- generatePrompt,
66
- type MemoryData,
67
- type PromptVariant,
68
- } from "./prompt-builder.js";
62
+ import type { PromptVariant } from "./prompt-builder.js";
69
63
 
70
64
  /**
71
65
  * Dependencies injected into tool handlers.
@@ -85,6 +79,155 @@ export interface ToolDeps {
85
79
  resetClient: () => void;
86
80
  }
87
81
 
82
+ // --- Memory Session Tracking ---
83
+ // Tracks memory operations per card session for visibility in AgentCardFooter
84
+
85
+ interface MemorySessionState {
86
+ cardId: string;
87
+ agentIdentifier: string;
88
+ agentName: string;
89
+ memoryReadCount: number;
90
+ pendingActions: { action: string; ts: string }[];
91
+ allActions: { action: string; ts: string }[];
92
+ dirty: boolean;
93
+ }
94
+
95
+ const memorySessions = new Map<string, MemorySessionState>();
96
+
97
+ function initMemorySession(
98
+ cardId: string,
99
+ agentIdentifier: string,
100
+ agentName: string,
101
+ ): void {
102
+ memorySessions.set(cardId, {
103
+ cardId,
104
+ agentIdentifier,
105
+ agentName,
106
+ memoryReadCount: 0,
107
+ pendingActions: [],
108
+ allActions: [],
109
+ dirty: false,
110
+ });
111
+ }
112
+
113
+ function getMemorySession(cardId: string): MemorySessionState | undefined {
114
+ return memorySessions.get(cardId);
115
+ }
116
+
117
+ function appendMemoryAction(cardId: string, action: string): void {
118
+ const session = memorySessions.get(cardId);
119
+ if (!session) return;
120
+ const truncated = action.length > 512 ? action.slice(0, 509) + "..." : action;
121
+ const entry = { action: truncated, ts: new Date().toISOString() };
122
+ session.pendingActions.push(entry);
123
+ session.dirty = true;
124
+ }
125
+
126
+ function incrementMemoryReads(cardId: string): void {
127
+ const session = memorySessions.get(cardId);
128
+ if (!session) return;
129
+ session.memoryReadCount++;
130
+ session.dirty = true;
131
+ }
132
+
133
+ /**
134
+ * Flush pending memory actions to the backend via updateAgentProgress.
135
+ * Fire-and-forget: errors are logged but never thrown.
136
+ */
137
+ async function flushMemoryActions(
138
+ client: HarmonyApiClient,
139
+ cardId: string,
140
+ ): Promise<void> {
141
+ const session = memorySessions.get(cardId);
142
+ if (!session || !session.dirty) return;
143
+
144
+ try {
145
+ // Batch reads into a single summary line
146
+ if (session.memoryReadCount > 0) {
147
+ session.allActions.push({
148
+ action: `Recalled ${session.memoryReadCount} memor${session.memoryReadCount === 1 ? "y" : "ies"}`,
149
+ ts: new Date().toISOString(),
150
+ });
151
+ session.memoryReadCount = 0;
152
+ }
153
+
154
+ // Move pending writes to allActions
155
+ if (session.pendingActions.length > 0) {
156
+ session.allActions.push(...session.pendingActions);
157
+ session.pendingActions = [];
158
+ }
159
+
160
+ // Trim to max 10 (drop oldest)
161
+ if (session.allActions.length > 10) {
162
+ session.allActions = session.allActions.slice(-10);
163
+ }
164
+
165
+ await client.updateAgentProgress(cardId, {
166
+ agentIdentifier: session.agentIdentifier,
167
+ agentName: session.agentName,
168
+ recentActions: session.allActions,
169
+ });
170
+
171
+ session.dirty = false;
172
+ } catch (err) {
173
+ // Fire-and-forget: log but don't propagate
174
+ console.error("[memory-session] flush failed:", err);
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Merge memory actions into an existing recentActions array from a progress update.
180
+ * Returns the merged array (caller's actions first, then memory actions appended).
181
+ */
182
+ function mergeMemoryActionsInto(
183
+ cardId: string,
184
+ callerActions: { action: string; ts: string }[],
185
+ ): { action: string; ts: string }[] {
186
+ const session = memorySessions.get(cardId);
187
+ if (!session) return callerActions;
188
+
189
+ // Flush reads into allActions
190
+ if (session.memoryReadCount > 0) {
191
+ session.allActions.push({
192
+ action: `Recalled ${session.memoryReadCount} memor${session.memoryReadCount === 1 ? "y" : "ies"}`,
193
+ ts: new Date().toISOString(),
194
+ });
195
+ session.memoryReadCount = 0;
196
+ }
197
+
198
+ // Move pending to allActions
199
+ if (session.pendingActions.length > 0) {
200
+ session.allActions.push(...session.pendingActions);
201
+ session.pendingActions = [];
202
+ }
203
+
204
+ // Merge: caller actions + memory actions, trim to 10
205
+ const merged = [...callerActions, ...session.allActions];
206
+ const trimmed = merged.length > 10 ? merged.slice(-10) : merged;
207
+
208
+ // Update allActions to the merged state
209
+ session.allActions = trimmed;
210
+ session.dirty = false;
211
+
212
+ return trimmed;
213
+ }
214
+
215
+ /**
216
+ * Get the first (and typically only) active memory session.
217
+ * Memory tools don't carry a cardId, so we find the active session.
218
+ */
219
+ function getActiveMemorySession(): MemorySessionState | undefined {
220
+ // Return the first active session (there should be at most one)
221
+ for (const session of memorySessions.values()) {
222
+ return session;
223
+ }
224
+ return undefined;
225
+ }
226
+
227
+ function cleanupMemorySession(cardId: string): void {
228
+ memorySessions.delete(cardId);
229
+ }
230
+
88
231
  // Tool definitions
89
232
  const TOOLS = {
90
233
  // Card operations
@@ -1838,7 +1981,7 @@ async function handleToolCall(
1838
1981
  if (!unauthenticatedTools.includes(name) && !deps.isConfigured()) {
1839
1982
  throw new Error(
1840
1983
  'Not configured. Run "npx @gethmy/mcp setup" to set your API key.\n' +
1841
- "You can generate an API key at https://gethmy.com → Settings → API Keys.\n" +
1984
+ "You can generate an API key at https://app.gethmy.com → Settings → API Keys.\n" +
1842
1985
  'Or use "harmony_onboard" to create an account and configure automatically.',
1843
1986
  );
1844
1987
  }
@@ -2267,7 +2410,10 @@ async function handleToolCall(
2267
2410
  });
2268
2411
 
2269
2412
  // Mark as explicit so auto-session won't interfere
2270
- markExplicit(cardId);
2413
+ markExplicit(cardId, { agentIdentifier, agentName });
2414
+
2415
+ // Initialize memory session tracking for action visibility
2416
+ initMemorySession(cardId, agentIdentifier, agentName);
2271
2417
 
2272
2418
  // Prefetch relevant context (non-blocking, best-effort)
2273
2419
  let prefetchedMemoryIds: string[] = [];
@@ -2348,6 +2494,21 @@ async function handleToolCall(
2348
2494
  args.progressPercent !== undefined
2349
2495
  ? z.number().min(0).max(100).parse(args.progressPercent)
2350
2496
  : undefined;
2497
+ // Merge any pending memory actions into the progress update
2498
+ const callerRecentActions = args.recentActions as
2499
+ | { action: string; ts: string }[]
2500
+ | undefined;
2501
+ const memSession = getMemorySession(cardId);
2502
+ let mergedRecentActions: { action: string; ts: string }[] | undefined;
2503
+ if (memSession?.dirty) {
2504
+ mergedRecentActions = mergeMemoryActionsInto(
2505
+ cardId,
2506
+ callerRecentActions || [],
2507
+ );
2508
+ } else if (callerRecentActions) {
2509
+ mergedRecentActions = callerRecentActions;
2510
+ }
2511
+
2351
2512
  const result = await client.updateAgentProgress(cardId, {
2352
2513
  agentIdentifier,
2353
2514
  agentName,
@@ -2363,6 +2524,7 @@ async function handleToolCall(
2363
2524
  estimatedMinutesRemaining: args.estimatedMinutesRemaining as
2364
2525
  | number
2365
2526
  | undefined,
2527
+ ...(mergedRecentActions && { recentActions: mergedRecentActions }),
2366
2528
  });
2367
2529
 
2368
2530
  // Mid-session learning extraction (fire-and-forget)
@@ -2405,6 +2567,10 @@ async function handleToolCall(
2405
2567
  ? z.number().min(0).max(100).parse(args.progressPercent)
2406
2568
  : undefined;
2407
2569
 
2570
+ // Final flush of any pending memory actions before ending the session
2571
+ await flushMemoryActions(client, cardId);
2572
+ cleanupMemorySession(cardId);
2573
+
2408
2574
  const result = await client.endAgentSession(cardId, {
2409
2575
  status: sessionStatus,
2410
2576
  progressPercent: endProgressPercent,
@@ -2527,14 +2693,11 @@ async function handleToolCall(
2527
2693
 
2528
2694
  // Prompt generation
2529
2695
  case "harmony_generate_prompt": {
2530
- // Get card - either by UUID or short ID
2531
- let cardData: CardData;
2532
- let columnData: ColumnData | null = null;
2696
+ // Resolve card ID either directly or via short ID
2697
+ let cardId: string;
2533
2698
 
2534
2699
  if (args.cardId) {
2535
- const cardId = z.string().uuid().parse(args.cardId);
2536
- const cardResult = await client.getCard(cardId);
2537
- cardData = cardResult.card as CardData;
2700
+ cardId = z.string().uuid().parse(args.cardId);
2538
2701
  } else if (args.shortId !== undefined) {
2539
2702
  const shortId = z.number().int().positive().parse(args.shortId);
2540
2703
  const projectId =
@@ -2545,35 +2708,12 @@ async function handleToolCall(
2545
2708
  );
2546
2709
  }
2547
2710
  const cardResult = await client.getCardByShortId(projectId, shortId);
2548
- cardData = cardResult.card as CardData;
2711
+ cardId = (cardResult.card as { id: string }).id;
2549
2712
  } else {
2550
2713
  throw new Error("Either cardId or shortId must be provided");
2551
2714
  }
2552
2715
 
2553
- // Try to get column info from board
2554
- const projectIdForBoard =
2555
- (args.projectId as string) ||
2556
- getActiveProjectId() ||
2557
- (cardData as unknown as { project_id: string }).project_id;
2558
- if (projectIdForBoard) {
2559
- try {
2560
- const board = await client.getBoard(projectIdForBoard, {
2561
- summary: true,
2562
- });
2563
- const columnId = (cardData as unknown as { column_id: string })
2564
- .column_id;
2565
- const column = (
2566
- board.columns as Array<{ id: string; name: string }>
2567
- ).find((col) => col.id === columnId);
2568
- if (column) {
2569
- columnData = { name: column.name };
2570
- }
2571
- } catch {
2572
- // Column info not available, continue without it
2573
- }
2574
- }
2575
-
2576
- const variant = (args.variant as PromptVariant) || "execute";
2716
+ // Parse MCP-specific context options
2577
2717
  const contextOptions: Record<string, boolean> = {};
2578
2718
  if (args.includeSubtasks !== undefined) {
2579
2719
  contextOptions.includeSubtasks =
@@ -2589,89 +2729,24 @@ async function handleToolCall(
2589
2729
  args.includeDescription === "true";
2590
2730
  }
2591
2731
 
2592
- // Assemble context using the context assembly engine
2593
- let assembledContextStr: string | undefined;
2594
- let assemblyId: string | undefined;
2595
- let memories: MemoryData[] | undefined;
2596
-
2597
- try {
2598
- const workspaceId = deps.getActiveWorkspaceId();
2599
- if (workspaceId && cardData.title) {
2600
- const cardLabels = (cardData.labels || []).map((l) => l.name);
2601
- const taskContext = [cardData.title, cardData.description || ""]
2602
- .filter(Boolean)
2603
- .join(" ");
2604
-
2605
- const assembled = await assembleContext({
2606
- workspaceId,
2607
- projectId: getActiveProjectId() || undefined,
2608
- taskContext,
2609
- cardLabels,
2610
- cardId: cardData.id,
2611
- client,
2612
- });
2613
-
2614
- if (assembled.context) {
2615
- assembledContextStr = assembled.context;
2616
- assemblyId = assembled.manifest.assemblyId;
2617
- cacheManifest(assembled.manifest);
2618
- }
2619
- }
2620
- } catch {
2621
- // Context assembly failed, try legacy fallback
2622
- try {
2623
- const workspaceId = deps.getActiveWorkspaceId();
2624
- if (workspaceId && cardData.title) {
2625
- const memoryResult = await client.searchMemoryEntities(
2626
- workspaceId,
2627
- cardData.title,
2628
- {
2629
- project_id: getActiveProjectId() || undefined,
2630
- limit: 5,
2631
- },
2632
- );
2633
- if (memoryResult.entities?.length > 0) {
2634
- memories = memoryResult.entities.map((e: unknown) => {
2635
- const entity = e as {
2636
- id: string;
2637
- type: string;
2638
- title: string;
2639
- content: string;
2640
- confidence: number;
2641
- tags: string[];
2642
- };
2643
- return {
2644
- id: entity.id,
2645
- type: entity.type,
2646
- title: entity.title,
2647
- content: entity.content,
2648
- confidence: entity.confidence,
2649
- tags: entity.tags || [],
2650
- };
2651
- });
2652
- }
2653
- }
2654
- } catch {
2655
- // Memory fetch also failed, continue without memories
2656
- }
2657
- }
2658
-
2659
- const result = generatePrompt({
2660
- card: cardData,
2661
- column: columnData,
2662
- variant,
2663
- contextOptions,
2732
+ // Delegate to the shared prompt generation pipeline
2733
+ const result = await client.generateCardPrompt({
2734
+ cardId,
2735
+ workspaceId: deps.getActiveWorkspaceId() || "",
2736
+ projectId:
2737
+ (args.projectId as string) || getActiveProjectId() || undefined,
2738
+ variant: (args.variant as PromptVariant) || "execute",
2664
2739
  customConstraints: args.customConstraints as string | undefined,
2665
- memories,
2666
- assembledContext: assembledContextStr,
2667
- assemblyId,
2740
+ contextOptions,
2668
2741
  });
2669
2742
 
2743
+ // MCP-specific: cache the assembly manifest for the feedback loop
2744
+ if (result.assemblyId) {
2745
+ trackSessionAssembly(cardId, result.assemblyId);
2746
+ }
2747
+
2670
2748
  return {
2671
2749
  success: true,
2672
- cardId: cardData.id,
2673
- shortId: cardData.short_id,
2674
- title: cardData.title,
2675
2750
  ...result,
2676
2751
  };
2677
2752
  }
@@ -2689,6 +2764,8 @@ async function handleToolCall(
2689
2764
  }
2690
2765
  const entityType = (args.type as string) || "context";
2691
2766
  const entityTags = (args.tags as string[]) || [];
2767
+ // Use session's agent identifier if available, otherwise null
2768
+ const activeMemSession = getActiveMemorySession();
2692
2769
  const result = await client.createMemoryEntity({
2693
2770
  workspace_id: workspaceId,
2694
2771
  project_id:
@@ -2704,7 +2781,7 @@ async function handleToolCall(
2704
2781
  ? z.number().min(0).max(1).parse(args.confidence)
2705
2782
  : undefined,
2706
2783
  tags: entityTags.length > 0 ? entityTags : undefined,
2707
- agent_identifier: "claude-code",
2784
+ agent_identifier: activeMemSession?.agentIdentifier || undefined,
2708
2785
  });
2709
2786
 
2710
2787
  // Fire-and-forget graph expansion: link new entity to semantically similar ones
@@ -2748,6 +2825,12 @@ async function handleToolCall(
2748
2825
  }
2749
2826
  }
2750
2827
 
2828
+ // Track memory write action and flush (fire-and-forget)
2829
+ if (activeMemSession) {
2830
+ appendMemoryAction(activeMemSession.cardId, `Stored memory: ${title}`);
2831
+ flushMemoryActions(client, activeMemSession.cardId).catch(() => {});
2832
+ }
2833
+
2751
2834
  return {
2752
2835
  success: true,
2753
2836
  ...result,
@@ -2830,6 +2913,12 @@ async function handleToolCall(
2830
2913
  ).catch(() => {});
2831
2914
  }
2832
2915
 
2916
+ // Track memory read (batched on flush)
2917
+ const recallMemSession = getActiveMemorySession();
2918
+ if (recallMemSession) {
2919
+ incrementMemoryReads(recallMemSession.cardId);
2920
+ }
2921
+
2833
2922
  return markdown || "No memories found.";
2834
2923
  }
2835
2924
 
@@ -2847,12 +2936,47 @@ async function handleToolCall(
2847
2936
  updates.confidence = z.number().min(0).max(1).parse(args.confidence);
2848
2937
  if (args.metadata !== undefined) updates.metadata = args.metadata;
2849
2938
  const result = await client.updateMemoryEntity(entityId, updates);
2939
+
2940
+ // Track memory write action and flush (fire-and-forget)
2941
+ const updateMemSession = getActiveMemorySession();
2942
+ if (updateMemSession) {
2943
+ const updateTitle = (updates.title as string) || entityId.slice(0, 8);
2944
+ appendMemoryAction(
2945
+ updateMemSession.cardId,
2946
+ `Updated memory: ${updateTitle}`,
2947
+ );
2948
+ flushMemoryActions(client, updateMemSession.cardId).catch(() => {});
2949
+ }
2950
+
2850
2951
  return { success: true, ...result };
2851
2952
  }
2852
2953
 
2853
2954
  case "harmony_forget": {
2854
2955
  const entityId = z.string().uuid().parse(args.entityId);
2956
+
2957
+ // Fetch title before deletion for action tracking
2958
+ let forgetTitle: string | null = null;
2959
+ const forgetMemSession = getActiveMemorySession();
2960
+ if (forgetMemSession) {
2961
+ try {
2962
+ const { entity } = await client.getMemoryEntity(entityId);
2963
+ forgetTitle = (entity as { title?: string })?.title || null;
2964
+ } catch {
2965
+ // Non-fatal: use truncated ID if fetch fails
2966
+ }
2967
+ }
2968
+
2855
2969
  await client.deleteMemoryEntity(entityId);
2970
+
2971
+ // Track memory write action and flush (fire-and-forget)
2972
+ if (forgetMemSession) {
2973
+ appendMemoryAction(
2974
+ forgetMemSession.cardId,
2975
+ `Removed memory: ${forgetTitle || entityId.slice(0, 8)}`,
2976
+ );
2977
+ flushMemoryActions(client, forgetMemSession.cardId).catch(() => {});
2978
+ }
2979
+
2856
2980
  return { success: true };
2857
2981
  }
2858
2982
 
@@ -2880,6 +3004,17 @@ async function handleToolCall(
2880
3004
  ? z.number().min(0).max(1).parse(args.confidence)
2881
3005
  : undefined,
2882
3006
  });
3007
+
3008
+ // Track memory write action and flush (fire-and-forget)
3009
+ const relateMemSession = getActiveMemorySession();
3010
+ if (relateMemSession) {
3011
+ appendMemoryAction(
3012
+ relateMemSession.cardId,
3013
+ `Linked memories: ${relationType}`,
3014
+ );
3015
+ flushMemoryActions(client, relateMemSession.cardId).catch(() => {});
3016
+ }
3017
+
2883
3018
  return { success: true, ...result };
2884
3019
  }
2885
3020
 
@@ -2904,6 +3039,13 @@ async function handleToolCall(
2904
3039
  limit: args.limit as number | undefined,
2905
3040
  },
2906
3041
  );
3042
+
3043
+ // Track memory read (batched on flush)
3044
+ const searchMemSession = getActiveMemorySession();
3045
+ if (searchMemSession) {
3046
+ incrementMemoryReads(searchMemSession.cardId);
3047
+ }
3048
+
2907
3049
  return markdown || "No memories found.";
2908
3050
  }
2909
3051
 
@@ -3039,7 +3181,7 @@ async function handleToolCall(
3039
3181
  });
3040
3182
 
3041
3183
  // Build URL for viewing the plan
3042
- const planUrl = `https://gethmy.com/plans/${(result.plan as { id: string }).id}`;
3184
+ const planUrl = `https://app.gethmy.com/plans/${(result.plan as { id: string }).id}`;
3043
3185
 
3044
3186
  return {
3045
3187
  success: true,
@@ -3813,13 +3955,17 @@ export class HarmonyMCPServer {
3813
3955
  await this.server.connect(transport);
3814
3956
  console.error("Harmony MCP server running on stdio");
3815
3957
 
3816
- // Initialize auto-session tracking
3958
+ // Initialize auto-session tracking with MCP client identity detection
3817
3959
  const configDeps = createConfigDeps();
3818
3960
  initAutoSession(
3819
3961
  async (client, cardId, status) => {
3820
3962
  await runEndSessionPipeline(client, configDeps, cardId, status);
3821
3963
  },
3822
3964
  () => getClient(),
3965
+ () => {
3966
+ const cv = this.server.getClientVersion();
3967
+ return cv ? { name: cv.name, version: cv.version } : null;
3968
+ },
3823
3969
  );
3824
3970
 
3825
3971
  // Graceful shutdown: end all auto-sessions
package/src/skills.ts CHANGED
@@ -251,7 +251,7 @@ Once you have the plan ID, call \`harmony_get_plan\` to fetch the full plan with
251
251
  ## [Plan Title]
252
252
  **Status:** draft/active/archived | **Phase:** plan/execute/verify/done
253
253
  **Tasks:** N total (X pending, Y in_progress, Z completed)
254
- **URL:** https://gethmy.com/plans/{id}
254
+ **URL:** https://app.gethmy.com/plans/{id}
255
255
  \\\`\\\`\\\`
256
256
 
257
257
  If plan is in execute phase and tasks already have linked cards, note which tasks have cards and which don't.
@@ -285,7 +285,7 @@ Only offer **(A) Single card**, **(C) Analyze only**, or **(D) Skip**.
285
285
  #### Option A — Single card
286
286
  1. Call \`harmony_create_card\` with:
287
287
  - \`title\`: Plan title
288
- - \`description\`: Brief 2-3 sentence summary of the plan + \`\\n\\n[View plan](https://gethmy.com/plans/{planId})\`
288
+ - \`description\`: Brief 2-3 sentence summary of the plan + \`\\n\\n[View plan](https://app.gethmy.com/plans/{planId})\`
289
289
  - \`priority\`: based on plan task priorities (use highest)
290
290
  2. Call \`harmony_update_plan\` to set \`status: "active"\`, \`workflowPhase: "execute"\`
291
291
 
@@ -407,7 +407,7 @@ Call \`harmony_create_plan\` with:
407
407
  ### 2B.5 — User Approval
408
408
 
409
409
  Show the user:
410
- - Plan URL: \`https://gethmy.com/plans/{id}\`
410
+ - Plan URL: \`https://app.gethmy.com/plans/{id}\`
411
411
  - Number of tasks created
412
412
  - Brief summary
413
413
 
package/src/tui/setup.ts CHANGED
@@ -41,7 +41,7 @@ export interface SetupOptions {
41
41
  const GLOBAL_SKILLS_DIR = join(homedir(), ".agents", "skills");
42
42
 
43
43
  // API base URL
44
- const API_URL = "https://gethmy.com/api";
44
+ const API_URL = "https://app.gethmy.com/api";
45
45
 
46
46
  /**
47
47
  * Register MCP server using Claude CLI
@@ -536,7 +536,7 @@ export async function runSetup(options: SetupOptions = {}): Promise<void> {
536
536
  if (!validation.valid) {
537
537
  spinner.stop(colors.error("API key validation failed"));
538
538
  p.log.error(validation.error || "Could not connect to Harmony API");
539
- p.log.info("Get an API key at: https://gethmy.com/user/keys");
539
+ p.log.info("Get an API key at: https://app.gethmy.com/user/keys");
540
540
  process.exit(1);
541
541
  }
542
542
 
@@ -974,7 +974,7 @@ export async function runSetup(options: SetupOptions = {}): Promise<void> {
974
974
  console.log("");
975
975
  console.log(` ${colors.dim("Add to new project: npx @gethmy/mcp setup")}`);
976
976
  console.log(
977
- ` ${colors.dim("Need help? Visit https://gethmy.com/docs/mcp")}`,
977
+ ` ${colors.dim("Need help? Visit https://app.gethmy.com/docs/mcp")}`,
978
978
  );
979
979
  console.log("");
980
980
  }