@gethmy/mcp 2.3.1 → 2.3.3
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/lib/api-client.js +2099 -648
- package/dist/lib/config.js +217 -201
- package/package.json +9 -5
- package/src/memory-cleanup.ts +2 -4
- package/dist/lib/__tests__/active-learning.test.js +0 -386
- package/dist/lib/__tests__/agent-performance-profiles.test.js +0 -325
- package/dist/lib/__tests__/auto-session.test.js +0 -661
- package/dist/lib/__tests__/context-assembly.test.js +0 -362
- package/dist/lib/__tests__/graph-expansion.test.js +0 -150
- package/dist/lib/__tests__/integration-memory-crud.test.js +0 -797
- package/dist/lib/__tests__/integration-memory-system.test.js +0 -281
- package/dist/lib/__tests__/lifecycle-maintenance.test.js +0 -207
- package/dist/lib/__tests__/pattern-detection.test.js +0 -295
- package/dist/lib/__tests__/prompt-builder.test.js +0 -418
- package/dist/lib/active-learning.js +0 -822
- package/dist/lib/auto-session.js +0 -214
- package/dist/lib/cli.js +0 -138
- package/dist/lib/consolidation.js +0 -303
- package/dist/lib/context-assembly.js +0 -884
- package/dist/lib/graph-expansion.js +0 -163
- package/dist/lib/http.js +0 -175
- package/dist/lib/index.js +0 -7
- package/dist/lib/lifecycle-maintenance.js +0 -88
- package/dist/lib/memory-cleanup.js +0 -455
- package/dist/lib/onboard.js +0 -36
- package/dist/lib/prompt-builder.js +0 -488
- package/dist/lib/remote.js +0 -166
- package/dist/lib/server.js +0 -3365
- package/dist/lib/skills.js +0 -593
- package/dist/lib/tui/agents.js +0 -116
- package/dist/lib/tui/docs.js +0 -744
- package/dist/lib/tui/setup.js +0 -934
- package/dist/lib/tui/theme.js +0 -95
- package/dist/lib/tui/writer.js +0 -200
package/dist/lib/tui/setup.js
DELETED
|
@@ -1,934 +0,0 @@
|
|
|
1
|
-
import { existsSync, lstatSync, mkdirSync, symlinkSync, unlinkSync, } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import * as p from "@clack/prompts";
|
|
5
|
-
import { areSkillsInstalled, getConfigPath, getLocalConfigPath, hasProjectContext, isConfigured, loadConfig, saveConfig, saveLocalConfig, setActiveProject, setActiveWorkspace, } from "../config.js";
|
|
6
|
-
import { onboardNewUser } from "../onboard.js";
|
|
7
|
-
import { buildSkillFile, HARMONY_WORKFLOW_PROMPT } from "../skills.js";
|
|
8
|
-
import { detectAgents } from "./agents.js";
|
|
9
|
-
import { runDocsStep } from "./docs.js";
|
|
10
|
-
import { colors, formatPath, messages } from "./theme.js";
|
|
11
|
-
import { getWriteSummary, writeFilesWithProgress } from "./writer.js";
|
|
12
|
-
// Central skills directory for global installation
|
|
13
|
-
const GLOBAL_SKILLS_DIR = join(homedir(), ".agents", "skills");
|
|
14
|
-
// API base URL
|
|
15
|
-
const API_URL = "https://app.gethmy.com/api";
|
|
16
|
-
/**
|
|
17
|
-
* Register MCP server using Claude CLI
|
|
18
|
-
* Returns true if successful, false if CLI unavailable or failed
|
|
19
|
-
*/
|
|
20
|
-
async function registerMcpServer() {
|
|
21
|
-
try {
|
|
22
|
-
const { execSync } = await import("node:child_process");
|
|
23
|
-
// Use the official CLI command to register the MCP server (npx for no global install)
|
|
24
|
-
execSync("claude mcp add --transport stdio harmony -- npx -y @gethmy/mcp@latest serve", {
|
|
25
|
-
stdio: "pipe",
|
|
26
|
-
});
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Write MCP server config directly to settings.json (fallback method)
|
|
35
|
-
*/
|
|
36
|
-
async function writeMcpConfigFallback(home) {
|
|
37
|
-
const { readFileSync, writeFileSync, mkdirSync, existsSync } = await import("node:fs");
|
|
38
|
-
const settingsPath = join(home, ".claude", "settings.json");
|
|
39
|
-
const settingsDir = dirname(settingsPath);
|
|
40
|
-
// Ensure directory exists
|
|
41
|
-
if (!existsSync(settingsDir)) {
|
|
42
|
-
mkdirSync(settingsDir, { recursive: true });
|
|
43
|
-
}
|
|
44
|
-
// Read existing settings or start fresh
|
|
45
|
-
let settings = {};
|
|
46
|
-
if (existsSync(settingsPath)) {
|
|
47
|
-
try {
|
|
48
|
-
settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
// Invalid JSON, start fresh
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
// Merge mcpServers config
|
|
55
|
-
const mcpServers = settings.mcpServers || {};
|
|
56
|
-
mcpServers.harmony = {
|
|
57
|
-
command: "npx",
|
|
58
|
-
args: ["-y", "@gethmy/mcp@latest", "serve"],
|
|
59
|
-
};
|
|
60
|
-
settings.mcpServers = mcpServers;
|
|
61
|
-
// Write back
|
|
62
|
-
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Validate API key by testing connectivity
|
|
66
|
-
*/
|
|
67
|
-
async function validateApiKey(apiKey, apiUrl = API_URL) {
|
|
68
|
-
try {
|
|
69
|
-
const response = await fetch(`${apiUrl}/v1/workspaces`, {
|
|
70
|
-
method: "GET",
|
|
71
|
-
headers: {
|
|
72
|
-
"Content-Type": "application/json",
|
|
73
|
-
"X-API-Key": apiKey,
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
if (!response.ok) {
|
|
77
|
-
const data = await response.json().catch(() => ({}));
|
|
78
|
-
return {
|
|
79
|
-
valid: false,
|
|
80
|
-
error: data.error || `API returned ${response.status}`,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
// Try to get user info from /me endpoint if available
|
|
84
|
-
const meResponse = await fetch(`${apiUrl}/v1/me`, {
|
|
85
|
-
method: "GET",
|
|
86
|
-
headers: {
|
|
87
|
-
"Content-Type": "application/json",
|
|
88
|
-
"X-API-Key": apiKey,
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
if (meResponse.ok) {
|
|
92
|
-
const meData = await meResponse.json();
|
|
93
|
-
return { valid: true, email: meData.user?.email };
|
|
94
|
-
}
|
|
95
|
-
return { valid: true };
|
|
96
|
-
}
|
|
97
|
-
catch (error) {
|
|
98
|
-
return {
|
|
99
|
-
valid: false,
|
|
100
|
-
error: error instanceof Error ? error.message : "Connection failed",
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Fetch workspaces from API
|
|
106
|
-
*/
|
|
107
|
-
async function fetchWorkspaces(apiKey) {
|
|
108
|
-
const response = await fetch(`${API_URL}/v1/workspaces`, {
|
|
109
|
-
method: "GET",
|
|
110
|
-
headers: {
|
|
111
|
-
"Content-Type": "application/json",
|
|
112
|
-
"X-API-Key": apiKey,
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
if (!response.ok) {
|
|
116
|
-
throw new Error(`Failed to fetch workspaces: ${response.status}`);
|
|
117
|
-
}
|
|
118
|
-
const data = await response.json();
|
|
119
|
-
return data.workspaces || [];
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Fetch projects from API
|
|
123
|
-
*/
|
|
124
|
-
async function fetchProjects(apiKey, workspaceId) {
|
|
125
|
-
const response = await fetch(`${API_URL}/v1/workspaces/${workspaceId}/projects`, {
|
|
126
|
-
method: "GET",
|
|
127
|
-
headers: {
|
|
128
|
-
"Content-Type": "application/json",
|
|
129
|
-
"X-API-Key": apiKey,
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
if (!response.ok) {
|
|
133
|
-
throw new Error(`Failed to fetch projects: ${response.status}`);
|
|
134
|
-
}
|
|
135
|
-
const data = await response.json();
|
|
136
|
-
return data.projects || [];
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Generate agent configuration files
|
|
140
|
-
*/
|
|
141
|
-
function getAgentFiles(agentId, cwd, installMode = "global") {
|
|
142
|
-
const home = homedir();
|
|
143
|
-
const files = [];
|
|
144
|
-
const symlinks = [];
|
|
145
|
-
switch (agentId) {
|
|
146
|
-
case "claude": {
|
|
147
|
-
// Claude Code skill files built from the central skill registry
|
|
148
|
-
const skillContent = buildSkillFile("hmy", "claude");
|
|
149
|
-
const planSkillContent = buildSkillFile("hmy-plan", "claude");
|
|
150
|
-
if (installMode === "global") {
|
|
151
|
-
// Global mode: Write skills to ~/.agents/skills/, symlink directories to ~/.claude/skills/
|
|
152
|
-
files.push({
|
|
153
|
-
path: join(GLOBAL_SKILLS_DIR, "hmy", "SKILL.md"),
|
|
154
|
-
content: skillContent,
|
|
155
|
-
type: "text",
|
|
156
|
-
});
|
|
157
|
-
symlinks.push({
|
|
158
|
-
target: join(GLOBAL_SKILLS_DIR, "hmy"),
|
|
159
|
-
link: join(home, ".claude", "skills", "hmy"),
|
|
160
|
-
});
|
|
161
|
-
files.push({
|
|
162
|
-
path: join(GLOBAL_SKILLS_DIR, "hmy-plan", "SKILL.md"),
|
|
163
|
-
content: planSkillContent,
|
|
164
|
-
type: "text",
|
|
165
|
-
});
|
|
166
|
-
symlinks.push({
|
|
167
|
-
target: join(GLOBAL_SKILLS_DIR, "hmy-plan"),
|
|
168
|
-
link: join(home, ".claude", "skills", "hmy-plan"),
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
// Local mode: Write skills directly to project directory
|
|
173
|
-
files.push({
|
|
174
|
-
path: join(cwd, ".claude", "skills", "hmy", "SKILL.md"),
|
|
175
|
-
content: skillContent,
|
|
176
|
-
type: "text",
|
|
177
|
-
});
|
|
178
|
-
files.push({
|
|
179
|
-
path: join(cwd, ".claude", "skills", "hmy-plan", "SKILL.md"),
|
|
180
|
-
content: planSkillContent,
|
|
181
|
-
type: "text",
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
// Note: MCP server registration is handled separately via `claude mcp add` CLI
|
|
185
|
-
// in runSetup() after file writing, with fallback to settings.json if CLI unavailable
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
case "codex": {
|
|
189
|
-
// AGENTS.md in project root (always local - project context)
|
|
190
|
-
const agentsContent = `# Harmony Integration
|
|
191
|
-
|
|
192
|
-
This project uses Harmony for task management. When working on tasks:
|
|
193
|
-
|
|
194
|
-
## Starting Work on a Card
|
|
195
|
-
|
|
196
|
-
When given a card reference (e.g., #42 or a card name), follow this workflow:
|
|
197
|
-
|
|
198
|
-
1. Use \`harmony_get_card_by_short_id\` or \`harmony_search_cards\` to find the card
|
|
199
|
-
2. Move the card to "In Progress" using \`harmony_move_card\`
|
|
200
|
-
3. Add the "agent" label using \`harmony_add_label_to_card\`
|
|
201
|
-
4. Start a session with \`harmony_start_agent_session\` (agentIdentifier: "codex", agentName: "OpenAI Codex")
|
|
202
|
-
5. Show the card details to the user
|
|
203
|
-
6. Use \`harmony_generate_prompt\` to get guidance, then implement the solution
|
|
204
|
-
7. Update progress periodically with \`harmony_update_agent_progress\`
|
|
205
|
-
8. When done, call \`harmony_end_agent_session\` and move to "Review"
|
|
206
|
-
|
|
207
|
-
## Auto-Detect Card for Implementation Tasks
|
|
208
|
-
|
|
209
|
-
Before implementing a plan or feature, check if it maps to an existing Harmony card:
|
|
210
|
-
|
|
211
|
-
1. Use \`harmony_search_cards\` with keywords from the task description
|
|
212
|
-
2. If a match is found, call \`harmony_start_agent_session\` (agentIdentifier: "claude-code", agentName: "Claude Code", moveToColumn: "In Progress", addLabels: ["agent"])
|
|
213
|
-
3. Update progress with \`harmony_update_agent_progress\` at milestones
|
|
214
|
-
4. When done, call \`harmony_end_agent_session\` with status: "completed", moveToColumn: "Review"
|
|
215
|
-
|
|
216
|
-
Skip if: work was already started with a card reference, or no matching card exists.
|
|
217
|
-
|
|
218
|
-
## Available Harmony Tools
|
|
219
|
-
|
|
220
|
-
- \`harmony_get_card\`, \`harmony_get_card_by_short_id\`, \`harmony_search_cards\` - Find cards
|
|
221
|
-
- \`harmony_move_card\` - Move cards between columns
|
|
222
|
-
- \`harmony_add_label_to_card\`, \`harmony_remove_label_from_card\` - Manage labels
|
|
223
|
-
- \`harmony_start_agent_session\`, \`harmony_update_agent_progress\`, \`harmony_end_agent_session\` - Track work
|
|
224
|
-
- \`harmony_get_board\` - Get board state
|
|
225
|
-
- \`harmony_generate_prompt\` - Get role-based guidance and focus areas for the card
|
|
226
|
-
`;
|
|
227
|
-
files.push({
|
|
228
|
-
path: join(cwd, "AGENTS.md"),
|
|
229
|
-
content: agentsContent,
|
|
230
|
-
type: "text",
|
|
231
|
-
});
|
|
232
|
-
// Codex prompt file
|
|
233
|
-
const promptContent = `---
|
|
234
|
-
name: hmy
|
|
235
|
-
description: Start working on a Harmony card
|
|
236
|
-
arguments:
|
|
237
|
-
- name: card
|
|
238
|
-
description: Card reference (#42, UUID, or name)
|
|
239
|
-
required: true
|
|
240
|
-
---
|
|
241
|
-
|
|
242
|
-
${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "{{card}}").replace("Your agent identifier", "codex").replace("Your agent name", "OpenAI Codex")}
|
|
243
|
-
`;
|
|
244
|
-
if (installMode === "global") {
|
|
245
|
-
// Global mode: Write prompt to central location, symlink to ~/.codex/prompts/
|
|
246
|
-
files.push({
|
|
247
|
-
path: join(GLOBAL_SKILLS_DIR, "codex", "hmy.md"),
|
|
248
|
-
content: promptContent,
|
|
249
|
-
type: "text",
|
|
250
|
-
});
|
|
251
|
-
symlinks.push({
|
|
252
|
-
target: join(GLOBAL_SKILLS_DIR, "codex", "hmy.md"),
|
|
253
|
-
link: join(home, ".codex", "prompts", "hmy.md"),
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
// Local mode: Write prompt directly to ~/.codex/prompts/
|
|
258
|
-
files.push({
|
|
259
|
-
path: join(home, ".codex", "prompts", "hmy.md"),
|
|
260
|
-
content: promptContent,
|
|
261
|
-
type: "text",
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
// Codex config.toml (always global)
|
|
265
|
-
const tomlContent = `
|
|
266
|
-
# Harmony MCP Server
|
|
267
|
-
[mcp_servers.harmony]
|
|
268
|
-
command = "npx"
|
|
269
|
-
args = ["-y", "@gethmy/mcp@latest", "serve"]
|
|
270
|
-
`;
|
|
271
|
-
files.push({
|
|
272
|
-
path: join(home, ".codex", "config.toml"),
|
|
273
|
-
content: tomlContent,
|
|
274
|
-
type: "toml",
|
|
275
|
-
tomlSection: "mcp_servers.harmony",
|
|
276
|
-
});
|
|
277
|
-
break;
|
|
278
|
-
}
|
|
279
|
-
case "cursor": {
|
|
280
|
-
// Cursor MCP config (always project-level)
|
|
281
|
-
files.push({
|
|
282
|
-
path: join(cwd, ".cursor", "mcp.json"),
|
|
283
|
-
content: JSON.stringify({
|
|
284
|
-
mcpServers: {
|
|
285
|
-
harmony: {
|
|
286
|
-
command: "npx",
|
|
287
|
-
args: ["-y", "@gethmy/mcp@latest", "serve"],
|
|
288
|
-
},
|
|
289
|
-
},
|
|
290
|
-
}, null, 2),
|
|
291
|
-
type: "json",
|
|
292
|
-
});
|
|
293
|
-
// Cursor rule file
|
|
294
|
-
const ruleContent = `---
|
|
295
|
-
description: Harmony card workflow rule
|
|
296
|
-
globs:
|
|
297
|
-
- "**/*"
|
|
298
|
-
alwaysApply: false
|
|
299
|
-
---
|
|
300
|
-
|
|
301
|
-
# Harmony Integration
|
|
302
|
-
|
|
303
|
-
When the user asks you to work on a Harmony card (references like #42, card names, or UUIDs):
|
|
304
|
-
|
|
305
|
-
${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Your agent identifier", "cursor").replace("Your agent name", "Cursor AI")}
|
|
306
|
-
`;
|
|
307
|
-
if (installMode === "global") {
|
|
308
|
-
// Global mode: Write rule to central location, symlink to ~/.cursor/rules/
|
|
309
|
-
files.push({
|
|
310
|
-
path: join(GLOBAL_SKILLS_DIR, "cursor", "harmony.mdc"),
|
|
311
|
-
content: ruleContent,
|
|
312
|
-
type: "text",
|
|
313
|
-
});
|
|
314
|
-
symlinks.push({
|
|
315
|
-
target: join(GLOBAL_SKILLS_DIR, "cursor", "harmony.mdc"),
|
|
316
|
-
link: join(home, ".cursor", "rules", "harmony.mdc"),
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
else {
|
|
320
|
-
// Local mode: Write rule directly to project directory
|
|
321
|
-
files.push({
|
|
322
|
-
path: join(cwd, ".cursor", "rules", "harmony.mdc"),
|
|
323
|
-
content: ruleContent,
|
|
324
|
-
type: "text",
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
break;
|
|
328
|
-
}
|
|
329
|
-
case "windsurf": {
|
|
330
|
-
// Windsurf global MCP config (always global)
|
|
331
|
-
files.push({
|
|
332
|
-
path: join(home, ".codeium", "windsurf", "mcp_config.json"),
|
|
333
|
-
content: JSON.stringify({
|
|
334
|
-
mcpServers: {
|
|
335
|
-
harmony: {
|
|
336
|
-
command: "npx",
|
|
337
|
-
args: ["-y", "@gethmy/mcp@latest", "serve"],
|
|
338
|
-
disabled: false,
|
|
339
|
-
alwaysAllow: [],
|
|
340
|
-
},
|
|
341
|
-
},
|
|
342
|
-
}, null, 2),
|
|
343
|
-
type: "json",
|
|
344
|
-
});
|
|
345
|
-
// Windsurf rule file
|
|
346
|
-
const ruleContent = `---
|
|
347
|
-
trigger: model_decision
|
|
348
|
-
description: Activate when user asks to work on a Harmony card (references like #42, card names, or task management)
|
|
349
|
-
---
|
|
350
|
-
|
|
351
|
-
# Harmony Card Workflow
|
|
352
|
-
|
|
353
|
-
When working on a Harmony card:
|
|
354
|
-
|
|
355
|
-
${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Your agent identifier", "windsurf").replace("Your agent name", "Windsurf AI")}
|
|
356
|
-
`;
|
|
357
|
-
if (installMode === "global") {
|
|
358
|
-
// Global mode: Write rule to central location, symlink to ~/.codeium/windsurf/rules/
|
|
359
|
-
files.push({
|
|
360
|
-
path: join(GLOBAL_SKILLS_DIR, "windsurf", "harmony.md"),
|
|
361
|
-
content: ruleContent,
|
|
362
|
-
type: "text",
|
|
363
|
-
});
|
|
364
|
-
symlinks.push({
|
|
365
|
-
target: join(GLOBAL_SKILLS_DIR, "windsurf", "harmony.md"),
|
|
366
|
-
link: join(home, ".codeium", "windsurf", "rules", "harmony.md"),
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
else {
|
|
370
|
-
// Local mode: Write rule directly to project directory
|
|
371
|
-
files.push({
|
|
372
|
-
path: join(cwd, ".windsurf", "rules", "harmony.md"),
|
|
373
|
-
content: ruleContent,
|
|
374
|
-
type: "text",
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
break;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
return { files, symlinks };
|
|
381
|
-
}
|
|
382
|
-
/**
|
|
383
|
-
* Main setup wizard with smart detection
|
|
384
|
-
*/
|
|
385
|
-
export async function runSetup(options = {}) {
|
|
386
|
-
const cwd = process.cwd();
|
|
387
|
-
const home = homedir();
|
|
388
|
-
console.clear();
|
|
389
|
-
console.log(messages.header());
|
|
390
|
-
// Check existing configuration
|
|
391
|
-
const existingConfig = loadConfig();
|
|
392
|
-
const alreadyConfigured = isConfigured();
|
|
393
|
-
const skillsStatus = areSkillsInstalled(cwd);
|
|
394
|
-
const hasContext = hasProjectContext(cwd);
|
|
395
|
-
// Track what we need to do
|
|
396
|
-
let needsApiKey = !alreadyConfigured;
|
|
397
|
-
let needsSkills = !skillsStatus.installed || options.force;
|
|
398
|
-
let needsContext = !hasContext && !options.skipContext;
|
|
399
|
-
// If workspace/project provided via flags, we'll set context
|
|
400
|
-
if (options.workspaceId || options.projectId) {
|
|
401
|
-
needsContext = true;
|
|
402
|
-
}
|
|
403
|
-
// Step 1: API Key (or create account)
|
|
404
|
-
let apiKey = options.apiKey || existingConfig.apiKey;
|
|
405
|
-
let userEmail = options.userEmail || existingConfig.userEmail || undefined;
|
|
406
|
-
let selectedWorkspaceIdFromSignup;
|
|
407
|
-
let selectedProjectIdFromSignup;
|
|
408
|
-
let selectedWorkspaceNameFromSignup;
|
|
409
|
-
let selectedProjectNameFromSignup;
|
|
410
|
-
let createdNewAccount = false;
|
|
411
|
-
if (needsApiKey || !apiKey || !apiKey.startsWith("hmy_")) {
|
|
412
|
-
// Determine path: create account or enter API key
|
|
413
|
-
let useNewAccount = options.newAccount === true;
|
|
414
|
-
if (!useNewAccount && options.apiKey) {
|
|
415
|
-
// API key passed via flag — skip the choice prompt
|
|
416
|
-
useNewAccount = false;
|
|
417
|
-
}
|
|
418
|
-
else if (!useNewAccount && !options.apiKey) {
|
|
419
|
-
const getStarted = await p.select({
|
|
420
|
-
message: "How would you like to get started?",
|
|
421
|
-
options: [
|
|
422
|
-
{
|
|
423
|
-
value: "create",
|
|
424
|
-
label: "Create a free account",
|
|
425
|
-
hint: "recommended for new users",
|
|
426
|
-
},
|
|
427
|
-
{
|
|
428
|
-
value: "apikey",
|
|
429
|
-
label: "I already have an API key",
|
|
430
|
-
},
|
|
431
|
-
],
|
|
432
|
-
});
|
|
433
|
-
if (p.isCancel(getStarted)) {
|
|
434
|
-
p.cancel("Setup cancelled");
|
|
435
|
-
process.exit(0);
|
|
436
|
-
}
|
|
437
|
-
useNewAccount = getStarted === "create";
|
|
438
|
-
}
|
|
439
|
-
if (useNewAccount) {
|
|
440
|
-
// --- Create account flow ---
|
|
441
|
-
const fullName = options.name ||
|
|
442
|
-
(await p.text({
|
|
443
|
-
message: "Full name",
|
|
444
|
-
placeholder: "Jane Smith",
|
|
445
|
-
validate: (v) => {
|
|
446
|
-
if (!v || v.trim().length === 0)
|
|
447
|
-
return "Name is required";
|
|
448
|
-
if (v.length > 100)
|
|
449
|
-
return "Name must be 100 characters or less";
|
|
450
|
-
return undefined;
|
|
451
|
-
},
|
|
452
|
-
}));
|
|
453
|
-
if (p.isCancel(fullName)) {
|
|
454
|
-
p.cancel("Setup cancelled");
|
|
455
|
-
process.exit(0);
|
|
456
|
-
}
|
|
457
|
-
const email = options.userEmail ||
|
|
458
|
-
(await p.text({
|
|
459
|
-
message: "Email",
|
|
460
|
-
placeholder: "you@example.com",
|
|
461
|
-
validate: (v) => {
|
|
462
|
-
if (!v)
|
|
463
|
-
return "Email is required";
|
|
464
|
-
if (v.length > 254)
|
|
465
|
-
return "Email is too long";
|
|
466
|
-
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v))
|
|
467
|
-
return "Invalid email format";
|
|
468
|
-
return undefined;
|
|
469
|
-
},
|
|
470
|
-
}));
|
|
471
|
-
if (p.isCancel(email)) {
|
|
472
|
-
p.cancel("Setup cancelled");
|
|
473
|
-
process.exit(0);
|
|
474
|
-
}
|
|
475
|
-
const password = (await p.password({
|
|
476
|
-
message: "Password",
|
|
477
|
-
validate: (v) => {
|
|
478
|
-
if (!v)
|
|
479
|
-
return "Password is required";
|
|
480
|
-
if (v.length < 8)
|
|
481
|
-
return "Password must be at least 8 characters";
|
|
482
|
-
if (v.length > 128)
|
|
483
|
-
return "Password must be 128 characters or less";
|
|
484
|
-
return undefined;
|
|
485
|
-
},
|
|
486
|
-
}));
|
|
487
|
-
if (p.isCancel(password)) {
|
|
488
|
-
p.cancel("Setup cancelled");
|
|
489
|
-
process.exit(0);
|
|
490
|
-
}
|
|
491
|
-
const spinner = p.spinner();
|
|
492
|
-
spinner.start("Creating your account...");
|
|
493
|
-
try {
|
|
494
|
-
const result = await onboardNewUser({
|
|
495
|
-
email: email,
|
|
496
|
-
password: password,
|
|
497
|
-
fullName: fullName,
|
|
498
|
-
});
|
|
499
|
-
spinner.stop(colors.success(`Account created for ${result.user.email}`));
|
|
500
|
-
apiKey = result.apiKey.rawKey;
|
|
501
|
-
userEmail = result.user.email;
|
|
502
|
-
selectedWorkspaceIdFromSignup = result.workspace.id;
|
|
503
|
-
selectedProjectIdFromSignup = result.project.id;
|
|
504
|
-
selectedWorkspaceNameFromSignup = result.workspace.name;
|
|
505
|
-
selectedProjectNameFromSignup = result.project.name;
|
|
506
|
-
createdNewAccount = true;
|
|
507
|
-
needsApiKey = true;
|
|
508
|
-
// Save config immediately
|
|
509
|
-
saveConfig({ apiKey, userEmail, apiUrl: API_URL });
|
|
510
|
-
setActiveWorkspace(selectedWorkspaceIdFromSignup);
|
|
511
|
-
setActiveProject(selectedProjectIdFromSignup);
|
|
512
|
-
p.log.success("Workspace and board created");
|
|
513
|
-
}
|
|
514
|
-
catch (error) {
|
|
515
|
-
spinner.stop(colors.error("Account creation failed"));
|
|
516
|
-
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
517
|
-
if (msg.includes("already") || msg.includes("409")) {
|
|
518
|
-
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'.");
|
|
519
|
-
}
|
|
520
|
-
else {
|
|
521
|
-
p.log.error(msg);
|
|
522
|
-
p.log.info("Please try again or visit https://app.gethmy.com");
|
|
523
|
-
}
|
|
524
|
-
process.exit(1);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
else {
|
|
528
|
-
// --- Existing API key flow ---
|
|
529
|
-
const keyInput = await p.text({
|
|
530
|
-
message: "Enter your Harmony API key",
|
|
531
|
-
placeholder: "hmy_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
532
|
-
validate: (value) => {
|
|
533
|
-
if (!value)
|
|
534
|
-
return "API key is required";
|
|
535
|
-
if (!value.startsWith("hmy_"))
|
|
536
|
-
return 'API key must start with "hmy_"';
|
|
537
|
-
if (value.length < 20)
|
|
538
|
-
return "API key is too short";
|
|
539
|
-
return undefined;
|
|
540
|
-
},
|
|
541
|
-
});
|
|
542
|
-
if (p.isCancel(keyInput)) {
|
|
543
|
-
p.cancel("Setup cancelled");
|
|
544
|
-
process.exit(0);
|
|
545
|
-
}
|
|
546
|
-
apiKey = keyInput;
|
|
547
|
-
needsApiKey = true;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
else {
|
|
551
|
-
p.log.success(`Using existing API key: ${apiKey.slice(0, 8)}...`);
|
|
552
|
-
}
|
|
553
|
-
// Validate API key (skip if we just created account — key is guaranteed valid)
|
|
554
|
-
const spinner = p.spinner();
|
|
555
|
-
if (!createdNewAccount) {
|
|
556
|
-
spinner.start("Validating API key...");
|
|
557
|
-
const validation = await validateApiKey(apiKey);
|
|
558
|
-
if (!validation.valid) {
|
|
559
|
-
spinner.stop(colors.error("API key validation failed"));
|
|
560
|
-
p.log.error(validation.error || "Could not connect to Harmony API");
|
|
561
|
-
p.log.info("Get an API key at: https://app.gethmy.com/user/keys");
|
|
562
|
-
process.exit(1);
|
|
563
|
-
}
|
|
564
|
-
if (!userEmail) {
|
|
565
|
-
userEmail = validation.email;
|
|
566
|
-
}
|
|
567
|
-
spinner.stop(colors.success(userEmail ? `Connected as ${userEmail}` : "API key validated"));
|
|
568
|
-
}
|
|
569
|
-
// Step 2: Check if skills are already installed
|
|
570
|
-
let selectedAgents = [];
|
|
571
|
-
let installMode = options.installMode || "global";
|
|
572
|
-
if (skillsStatus.installed && !options.force) {
|
|
573
|
-
p.log.success(`Skills already installed (${skillsStatus.location})`);
|
|
574
|
-
const reinstall = await p.confirm({
|
|
575
|
-
message: "Reinstall skills?",
|
|
576
|
-
initialValue: false,
|
|
577
|
-
});
|
|
578
|
-
if (p.isCancel(reinstall)) {
|
|
579
|
-
p.cancel("Setup cancelled");
|
|
580
|
-
process.exit(0);
|
|
581
|
-
}
|
|
582
|
-
needsSkills = reinstall;
|
|
583
|
-
}
|
|
584
|
-
if (needsSkills) {
|
|
585
|
-
// Detect and select agents
|
|
586
|
-
const detectedAgents = detectAgents(cwd);
|
|
587
|
-
if (options.agents && options.agents.length > 0) {
|
|
588
|
-
selectedAgents = options.agents;
|
|
589
|
-
}
|
|
590
|
-
else {
|
|
591
|
-
const agentOptions = detectedAgents.map((agent) => ({
|
|
592
|
-
value: agent.id,
|
|
593
|
-
label: agent.name,
|
|
594
|
-
hint: agent.detected
|
|
595
|
-
? colors.success(`${agent.description} (detected)`)
|
|
596
|
-
: colors.dim(`${agent.description}`),
|
|
597
|
-
}));
|
|
598
|
-
const agentSelection = await p.multiselect({
|
|
599
|
-
message: "Select agents to configure",
|
|
600
|
-
options: agentOptions,
|
|
601
|
-
initialValues: detectedAgents
|
|
602
|
-
.filter((a) => a.detected)
|
|
603
|
-
.map((a) => a.id),
|
|
604
|
-
required: true,
|
|
605
|
-
});
|
|
606
|
-
if (p.isCancel(agentSelection)) {
|
|
607
|
-
p.cancel("Setup cancelled");
|
|
608
|
-
process.exit(0);
|
|
609
|
-
}
|
|
610
|
-
selectedAgents = agentSelection;
|
|
611
|
-
}
|
|
612
|
-
if (selectedAgents.length === 0) {
|
|
613
|
-
p.log.warning("No agents selected. Skipping skills installation.");
|
|
614
|
-
needsSkills = false;
|
|
615
|
-
}
|
|
616
|
-
else if (!options.installMode) {
|
|
617
|
-
// Select install mode
|
|
618
|
-
const modeSelection = await p.select({
|
|
619
|
-
message: "Where should Harmony skills be installed?",
|
|
620
|
-
options: [
|
|
621
|
-
{
|
|
622
|
-
value: "global",
|
|
623
|
-
label: "Global (shared) - Recommended",
|
|
624
|
-
hint: "Skills in ~/.agents/skills/, available in all projects",
|
|
625
|
-
},
|
|
626
|
-
{
|
|
627
|
-
value: "local",
|
|
628
|
-
label: "Local (project directory)",
|
|
629
|
-
hint: "Skills in .claude/skills/, committed to git",
|
|
630
|
-
},
|
|
631
|
-
],
|
|
632
|
-
initialValue: "global",
|
|
633
|
-
});
|
|
634
|
-
if (p.isCancel(modeSelection)) {
|
|
635
|
-
p.cancel("Setup cancelled");
|
|
636
|
-
process.exit(0);
|
|
637
|
-
}
|
|
638
|
-
installMode = modeSelection;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
// Step 3: Workspace and Project Selection
|
|
642
|
-
let selectedWorkspaceId = selectedWorkspaceIdFromSignup || options.workspaceId;
|
|
643
|
-
let selectedProjectId = selectedProjectIdFromSignup || options.projectId;
|
|
644
|
-
let selectedWorkspaceName = selectedWorkspaceNameFromSignup;
|
|
645
|
-
let selectedProjectName = selectedProjectNameFromSignup;
|
|
646
|
-
// Skip context selection if we just created a new account
|
|
647
|
-
if (createdNewAccount) {
|
|
648
|
-
needsContext = false;
|
|
649
|
-
}
|
|
650
|
-
if (needsContext && !options.skipContext) {
|
|
651
|
-
// Fetch workspaces
|
|
652
|
-
spinner.start("Fetching workspaces...");
|
|
653
|
-
let workspaces = [];
|
|
654
|
-
try {
|
|
655
|
-
workspaces = await fetchWorkspaces(apiKey);
|
|
656
|
-
spinner.stop(colors.success(`Found ${workspaces.length} workspace(s)`));
|
|
657
|
-
}
|
|
658
|
-
catch (_error) {
|
|
659
|
-
spinner.stop(colors.warning("Could not fetch workspaces"));
|
|
660
|
-
p.log.warning("Skipping workspace/project selection. You can set this later.");
|
|
661
|
-
needsContext = false;
|
|
662
|
-
}
|
|
663
|
-
if (needsContext && workspaces.length > 0) {
|
|
664
|
-
// Select workspace
|
|
665
|
-
if (!selectedWorkspaceId) {
|
|
666
|
-
const workspaceOptions = workspaces.map((ws) => ({
|
|
667
|
-
value: ws.id,
|
|
668
|
-
label: ws.name,
|
|
669
|
-
}));
|
|
670
|
-
const workspaceSelection = await p.select({
|
|
671
|
-
message: "Select workspace",
|
|
672
|
-
options: workspaceOptions,
|
|
673
|
-
});
|
|
674
|
-
if (p.isCancel(workspaceSelection)) {
|
|
675
|
-
p.cancel("Setup cancelled");
|
|
676
|
-
process.exit(0);
|
|
677
|
-
}
|
|
678
|
-
selectedWorkspaceId = workspaceSelection;
|
|
679
|
-
}
|
|
680
|
-
selectedWorkspaceName = workspaces.find((w) => w.id === selectedWorkspaceId)?.name;
|
|
681
|
-
// Fetch and select project
|
|
682
|
-
spinner.start("Fetching projects...");
|
|
683
|
-
let projects = [];
|
|
684
|
-
try {
|
|
685
|
-
projects = await fetchProjects(apiKey, selectedWorkspaceId);
|
|
686
|
-
spinner.stop(colors.success(`Found ${projects.length} project(s)`));
|
|
687
|
-
}
|
|
688
|
-
catch (_error) {
|
|
689
|
-
spinner.stop(colors.warning("Could not fetch projects"));
|
|
690
|
-
p.log.warning("Skipping project selection. You can set this later.");
|
|
691
|
-
}
|
|
692
|
-
if (projects.length > 0 && !selectedProjectId) {
|
|
693
|
-
const projectOptions = projects.map((proj) => ({
|
|
694
|
-
value: proj.id,
|
|
695
|
-
label: proj.name,
|
|
696
|
-
hint: proj.description
|
|
697
|
-
? colors.dim(proj.description.slice(0, 50))
|
|
698
|
-
: undefined,
|
|
699
|
-
}));
|
|
700
|
-
const projectSelection = await p.select({
|
|
701
|
-
message: "Select project",
|
|
702
|
-
options: projectOptions,
|
|
703
|
-
});
|
|
704
|
-
if (p.isCancel(projectSelection)) {
|
|
705
|
-
p.cancel("Setup cancelled");
|
|
706
|
-
process.exit(0);
|
|
707
|
-
}
|
|
708
|
-
selectedProjectId = projectSelection;
|
|
709
|
-
selectedProjectName = projects.find((p) => p.id === selectedProjectId)?.name;
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
// Step 4: Collect all files and symlinks to create
|
|
714
|
-
const allFiles = [];
|
|
715
|
-
const allSymlinks = [];
|
|
716
|
-
// Always save global config if we have an API key change
|
|
717
|
-
if (needsApiKey || !alreadyConfigured) {
|
|
718
|
-
allFiles.push({
|
|
719
|
-
path: getConfigPath(),
|
|
720
|
-
content: JSON.stringify({
|
|
721
|
-
apiKey,
|
|
722
|
-
apiUrl: API_URL,
|
|
723
|
-
userEmail: userEmail || null,
|
|
724
|
-
activeWorkspaceId: null,
|
|
725
|
-
activeProjectId: null,
|
|
726
|
-
}, null, 2),
|
|
727
|
-
type: "text", // Use text to avoid merging
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
// Project docs scaffold / verification
|
|
731
|
-
if (!options.skipDocs) {
|
|
732
|
-
const docsResult = await runDocsStep(cwd);
|
|
733
|
-
if (!docsResult.skipped) {
|
|
734
|
-
for (const file of docsResult.files) {
|
|
735
|
-
allFiles.push(file);
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
// Agent-specific files
|
|
740
|
-
if (needsSkills && selectedAgents.length > 0) {
|
|
741
|
-
for (const agentId of selectedAgents) {
|
|
742
|
-
const { files, symlinks } = getAgentFiles(agentId, cwd, installMode);
|
|
743
|
-
allFiles.push(...files);
|
|
744
|
-
allSymlinks.push(...symlinks);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
// Step 5: Show summary
|
|
748
|
-
const detectedAgents = detectAgents(cwd);
|
|
749
|
-
console.log("");
|
|
750
|
-
p.log.step("Summary");
|
|
751
|
-
console.log("");
|
|
752
|
-
console.log(` ${colors.bold("API Key:")} ${apiKey.slice(0, 8)}...`);
|
|
753
|
-
if (userEmail) {
|
|
754
|
-
console.log(` ${colors.bold("Email:")} ${userEmail}`);
|
|
755
|
-
}
|
|
756
|
-
if (needsSkills && selectedAgents.length > 0) {
|
|
757
|
-
console.log(` ${colors.bold("Agents:")} ${selectedAgents.map((a) => detectedAgents.find((d) => d.id === a)?.name).join(", ")}`);
|
|
758
|
-
console.log(` ${colors.bold("Install:")} ${installMode === "global" ? "Global (~/.agents/skills/)" : "Local (project)"}`);
|
|
759
|
-
}
|
|
760
|
-
else {
|
|
761
|
-
console.log(` ${colors.bold("Skills:")} Already installed (${skillsStatus.location || "none"})`);
|
|
762
|
-
}
|
|
763
|
-
if (selectedWorkspaceId) {
|
|
764
|
-
console.log(` ${colors.bold("Workspace:")} ${selectedWorkspaceName || selectedWorkspaceId}`);
|
|
765
|
-
}
|
|
766
|
-
if (selectedProjectId) {
|
|
767
|
-
console.log(` ${colors.bold("Project:")} ${selectedProjectName || selectedProjectId}`);
|
|
768
|
-
}
|
|
769
|
-
if (allFiles.length > 0) {
|
|
770
|
-
const summary = getWriteSummary(allFiles, {
|
|
771
|
-
force: options.force || needsSkills,
|
|
772
|
-
});
|
|
773
|
-
if (summary.toCreate.length > 0) {
|
|
774
|
-
console.log("");
|
|
775
|
-
console.log(` ${colors.success("Files to create:")}`);
|
|
776
|
-
for (const path of summary.toCreate) {
|
|
777
|
-
console.log(` ${colors.dim("\u2022")} ${path}`);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
if (allSymlinks.length > 0) {
|
|
781
|
-
console.log("");
|
|
782
|
-
console.log(` ${colors.info("Symlinks to create:")}`);
|
|
783
|
-
for (const symlink of allSymlinks) {
|
|
784
|
-
console.log(` ${colors.dim("\u{1F517}")} ${formatPath(symlink.link, home)} → ${formatPath(symlink.target, home)}`);
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
if (summary.toUpdate.length > 0) {
|
|
788
|
-
console.log("");
|
|
789
|
-
console.log(` ${colors.info("Files to update:")}`);
|
|
790
|
-
for (const path of summary.toUpdate) {
|
|
791
|
-
console.log(` ${colors.dim("\u2022")} ${path}`);
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
if (summary.toSkip.length > 0 && !options.force) {
|
|
795
|
-
console.log("");
|
|
796
|
-
console.log(` ${colors.dim("Files to skip (already exist):")}`);
|
|
797
|
-
for (const path of summary.toSkip) {
|
|
798
|
-
console.log(` ${colors.dim("\u2022")} ${path}`);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
console.log("");
|
|
803
|
-
// Step 6: Confirm and execute
|
|
804
|
-
const shouldProceed = await p.confirm({
|
|
805
|
-
message: "Proceed with setup?",
|
|
806
|
-
initialValue: true,
|
|
807
|
-
});
|
|
808
|
-
if (p.isCancel(shouldProceed) || !shouldProceed) {
|
|
809
|
-
p.cancel("Setup cancelled");
|
|
810
|
-
process.exit(0);
|
|
811
|
-
}
|
|
812
|
-
console.log("");
|
|
813
|
-
// Step 7: Write files
|
|
814
|
-
// Force-write when user chose to reinstall skills — skill files are
|
|
815
|
-
// package-generated content that should always match the installed version.
|
|
816
|
-
if (allFiles.length > 0) {
|
|
817
|
-
await writeFilesWithProgress(allFiles, {
|
|
818
|
-
force: options.force || needsSkills,
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
// Step 8: Create symlinks
|
|
822
|
-
if (allSymlinks.length > 0) {
|
|
823
|
-
for (const symlink of allSymlinks) {
|
|
824
|
-
try {
|
|
825
|
-
// Ensure parent directory exists
|
|
826
|
-
const linkDir = dirname(symlink.link);
|
|
827
|
-
if (!existsSync(linkDir)) {
|
|
828
|
-
mkdirSync(linkDir, { recursive: true });
|
|
829
|
-
}
|
|
830
|
-
// Check if link already exists
|
|
831
|
-
let linkExists = false;
|
|
832
|
-
try {
|
|
833
|
-
lstatSync(symlink.link);
|
|
834
|
-
linkExists = true;
|
|
835
|
-
}
|
|
836
|
-
catch {
|
|
837
|
-
// Link doesn't exist
|
|
838
|
-
}
|
|
839
|
-
if (linkExists) {
|
|
840
|
-
if (options.force) {
|
|
841
|
-
unlinkSync(symlink.link);
|
|
842
|
-
}
|
|
843
|
-
else {
|
|
844
|
-
// Skip existing symlink
|
|
845
|
-
continue;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
symlinkSync(symlink.target, symlink.link);
|
|
849
|
-
}
|
|
850
|
-
catch {
|
|
851
|
-
p.log.warning(`Failed to create symlink: ${symlink.link}`);
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
// Step 8b: Register MCP server for Claude (project-local scope)
|
|
856
|
-
const claudeDetected = detectAgents(cwd).some((a) => a.id === "claude" && a.detected);
|
|
857
|
-
if (claudeDetected || selectedAgents.includes("claude")) {
|
|
858
|
-
const mcpRegistered = await registerMcpServer();
|
|
859
|
-
if (mcpRegistered) {
|
|
860
|
-
console.log(` ${colors.success("\u2713")} ${colors.dim("MCP server registered via claude CLI")}`);
|
|
861
|
-
}
|
|
862
|
-
else {
|
|
863
|
-
// Fallback: write directly to settings.json
|
|
864
|
-
try {
|
|
865
|
-
await writeMcpConfigFallback(home);
|
|
866
|
-
console.log(` ${colors.success("\u2713")} ${colors.dim(formatPath(join(home, ".claude", "settings.json"), home))} ${colors.dim("(updated)")}`);
|
|
867
|
-
}
|
|
868
|
-
catch {
|
|
869
|
-
p.log.warning("Could not register MCP server. Run manually: claude mcp add --transport stdio harmony -- npx -y @gethmy/mcp@latest serve");
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
// Step 9: Save local context
|
|
874
|
-
if (selectedWorkspaceId || selectedProjectId) {
|
|
875
|
-
const localConfig = {};
|
|
876
|
-
if (selectedWorkspaceId)
|
|
877
|
-
localConfig.workspaceId = selectedWorkspaceId;
|
|
878
|
-
if (selectedProjectId)
|
|
879
|
-
localConfig.projectId = selectedProjectId;
|
|
880
|
-
saveLocalConfig(localConfig, cwd);
|
|
881
|
-
console.log(` ${colors.success("\u2713")} ${colors.dim(formatPath(getLocalConfigPath(cwd), home))} ${colors.dim("(created)")}`);
|
|
882
|
-
}
|
|
883
|
-
// Step 10: Show completion message
|
|
884
|
-
console.log("");
|
|
885
|
-
p.outro(colors.success("Setup complete!"));
|
|
886
|
-
if (createdNewAccount && selectedWorkspaceNameFromSignup) {
|
|
887
|
-
// New account: show board URL and next steps
|
|
888
|
-
const wsSlug = selectedWorkspaceNameFromSignup
|
|
889
|
-
.toLowerCase()
|
|
890
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
891
|
-
.replace(/(^-|-$)/g, "");
|
|
892
|
-
const projSlug = (selectedProjectNameFromSignup || "my-first-board")
|
|
893
|
-
.toLowerCase()
|
|
894
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
895
|
-
.replace(/(^-|-$)/g, "");
|
|
896
|
-
console.log("");
|
|
897
|
-
console.log(` ${colors.bold("Your board:")} ${colors.highlight(`https://app.gethmy.com/${wsSlug}/${projSlug}`)}`);
|
|
898
|
-
console.log("");
|
|
899
|
-
console.log(` ${colors.bold("Next steps:")}`);
|
|
900
|
-
console.log(` 1. Open Claude Code and say: ${colors.highlight('"Show me my board"')}`);
|
|
901
|
-
console.log(` 2. Create a card: ${colors.highlight('"Create a card called Auth token refresh"')}`);
|
|
902
|
-
console.log(` 3. Start the daemon: ${colors.highlight("npx @gethmy/agent")}`);
|
|
903
|
-
console.log("");
|
|
904
|
-
console.log(` ${colors.dim("Happy shipping!")}`);
|
|
905
|
-
}
|
|
906
|
-
else {
|
|
907
|
-
// Existing user: show config paths and usage
|
|
908
|
-
console.log("");
|
|
909
|
-
console.log(` ${colors.bold("Configuration:")}`);
|
|
910
|
-
console.log(` API key: ${formatPath(getConfigPath(), home)}`);
|
|
911
|
-
if (needsSkills && selectedAgents.length > 0) {
|
|
912
|
-
console.log(` Skills: ${installMode === "global" ? "~/.agents/skills/ (global)" : ".claude/skills/ (local)"}`);
|
|
913
|
-
}
|
|
914
|
-
if (selectedWorkspaceId || selectedProjectId) {
|
|
915
|
-
console.log(` Context: ${formatPath(getLocalConfigPath(cwd), home)}`);
|
|
916
|
-
}
|
|
917
|
-
console.log("");
|
|
918
|
-
console.log(` ${colors.bold("Usage:")}`);
|
|
919
|
-
if (!needsSkills || selectedAgents.includes("claude")) {
|
|
920
|
-
console.log(` ${colors.brand("Claude Code:")} ${colors.highlight("/hmy #42")} or ${colors.highlight("/hmy-plan")} ${colors.dim("(create or execute plans)")}`);
|
|
921
|
-
}
|
|
922
|
-
if (selectedAgents.includes("codex")) {
|
|
923
|
-
console.log(` ${colors.brand("Codex:")} ${colors.highlight("/prompts:hmy #42")}`);
|
|
924
|
-
}
|
|
925
|
-
if (selectedAgents.includes("cursor") ||
|
|
926
|
-
selectedAgents.includes("windsurf")) {
|
|
927
|
-
console.log(` ${colors.brand("Cursor:")} MCP tools available automatically`);
|
|
928
|
-
}
|
|
929
|
-
console.log("");
|
|
930
|
-
console.log(` ${colors.dim("Add to new project: npx @gethmy/mcp setup")}`);
|
|
931
|
-
console.log(` ${colors.dim("Need help? Visit https://app.gethmy.com/docs/mcp")}`);
|
|
932
|
-
}
|
|
933
|
-
console.log("");
|
|
934
|
-
}
|