@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
|
@@ -29,16 +29,16 @@ describe("auto-start", () => {
|
|
|
29
29
|
await trackActivity(CARD_A, { autoStart: true, client: client });
|
|
30
30
|
expect(client.startAgentSession).toHaveBeenCalledTimes(1);
|
|
31
31
|
expect(client.startAgentSession).toHaveBeenCalledWith(CARD_A, {
|
|
32
|
-
agentIdentifier: "
|
|
33
|
-
agentName: "
|
|
32
|
+
agentIdentifier: "unknown",
|
|
33
|
+
agentName: "Unknown Agent",
|
|
34
34
|
status: "working",
|
|
35
35
|
});
|
|
36
36
|
expect(getActiveSessions().size).toBe(1);
|
|
37
37
|
const session = getActiveSessions().get(CARD_A);
|
|
38
38
|
expect(session).toBeDefined();
|
|
39
39
|
expect(session.isExplicit).toBe(false);
|
|
40
|
-
expect(session.agentIdentifier).toBe("
|
|
41
|
-
expect(session.agentName).toBe("
|
|
40
|
+
expect(session.agentIdentifier).toBe("unknown");
|
|
41
|
+
expect(session.agentName).toBe("Unknown Agent");
|
|
42
42
|
});
|
|
43
43
|
test("does NOT trigger on autoStart=false", async () => {
|
|
44
44
|
const client = makeMockClient();
|
|
@@ -68,7 +68,7 @@ describe("auto-start", () => {
|
|
|
68
68
|
await trackActivity(CARD_A, { autoStart: true, client: client });
|
|
69
69
|
// Should still be tracked despite API error
|
|
70
70
|
expect(getActiveSessions().size).toBe(1);
|
|
71
|
-
expect(getActiveSessions().get(CARD_A).agentIdentifier).toBe("
|
|
71
|
+
expect(getActiveSessions().get(CARD_A).agentIdentifier).toBe("unknown");
|
|
72
72
|
});
|
|
73
73
|
test("uses clientGetter when no client in options", async () => {
|
|
74
74
|
const client = makeMockClient();
|
|
@@ -222,8 +222,8 @@ describe("inactivity timeout", () => {
|
|
|
222
222
|
startedAt: Date.now() - INACTIVITY_TIMEOUT_MS - 1000,
|
|
223
223
|
lastActivityAt: Date.now() - INACTIVITY_TIMEOUT_MS - 1000,
|
|
224
224
|
isExplicit: false,
|
|
225
|
-
agentIdentifier: "
|
|
226
|
-
agentName: "
|
|
225
|
+
agentIdentifier: "unknown",
|
|
226
|
+
agentName: "Unknown Agent",
|
|
227
227
|
});
|
|
228
228
|
checkInactivity();
|
|
229
229
|
// autoEndSession is fire-and-forget in checkInactivity, wait for it
|
|
@@ -241,8 +241,8 @@ describe("inactivity timeout", () => {
|
|
|
241
241
|
startedAt: Date.now(),
|
|
242
242
|
lastActivityAt: Date.now(), // just now — well within timeout
|
|
243
243
|
isExplicit: false,
|
|
244
|
-
agentIdentifier: "
|
|
245
|
-
agentName: "
|
|
244
|
+
agentIdentifier: "unknown",
|
|
245
|
+
agentName: "Unknown Agent",
|
|
246
246
|
});
|
|
247
247
|
checkInactivity();
|
|
248
248
|
expect(client.endAgentSession).not.toHaveBeenCalled();
|
|
@@ -273,8 +273,8 @@ describe("inactivity timeout", () => {
|
|
|
273
273
|
startedAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
|
|
274
274
|
lastActivityAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
|
|
275
275
|
isExplicit: false,
|
|
276
|
-
agentIdentifier: "
|
|
277
|
-
agentName: "
|
|
276
|
+
agentIdentifier: "unknown",
|
|
277
|
+
agentName: "Unknown Agent",
|
|
278
278
|
});
|
|
279
279
|
// CARD_B — still active
|
|
280
280
|
getActiveSessions().set(CARD_B, {
|
|
@@ -282,8 +282,8 @@ describe("inactivity timeout", () => {
|
|
|
282
282
|
startedAt: Date.now() - 1000,
|
|
283
283
|
lastActivityAt: Date.now() - 1000,
|
|
284
284
|
isExplicit: false,
|
|
285
|
-
agentIdentifier: "
|
|
286
|
-
agentName: "
|
|
285
|
+
agentIdentifier: "unknown",
|
|
286
|
+
agentName: "Unknown Agent",
|
|
287
287
|
});
|
|
288
288
|
// CARD_C — timed out but explicit
|
|
289
289
|
getActiveSessions().set(CARD_C, {
|
|
@@ -312,8 +312,8 @@ describe("inactivity timeout", () => {
|
|
|
312
312
|
startedAt: 0,
|
|
313
313
|
lastActivityAt: 0,
|
|
314
314
|
isExplicit: false,
|
|
315
|
-
agentIdentifier: "
|
|
316
|
-
agentName: "
|
|
315
|
+
agentIdentifier: "unknown",
|
|
316
|
+
agentName: "Unknown Agent",
|
|
317
317
|
});
|
|
318
318
|
// Should not throw
|
|
319
319
|
checkInactivity();
|
|
@@ -330,8 +330,8 @@ describe("inactivity timeout", () => {
|
|
|
330
330
|
startedAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
|
|
331
331
|
lastActivityAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
|
|
332
332
|
isExplicit: false,
|
|
333
|
-
agentIdentifier: "
|
|
334
|
-
agentName: "
|
|
333
|
+
agentIdentifier: "unknown",
|
|
334
|
+
agentName: "Unknown Agent",
|
|
335
335
|
});
|
|
336
336
|
// Refresh activity
|
|
337
337
|
await trackActivity(CARD_A, { autoStart: false });
|
|
@@ -351,8 +351,8 @@ describe("markExplicit", () => {
|
|
|
351
351
|
expect(getActiveSessions().get(CARD_A).isExplicit).toBe(false);
|
|
352
352
|
markExplicit(CARD_A);
|
|
353
353
|
expect(getActiveSessions().get(CARD_A).isExplicit).toBe(true);
|
|
354
|
-
// agentIdentifier should remain "
|
|
355
|
-
expect(getActiveSessions().get(CARD_A).agentIdentifier).toBe("
|
|
354
|
+
// agentIdentifier should remain "unknown" since no clientInfo was provided
|
|
355
|
+
expect(getActiveSessions().get(CARD_A).agentIdentifier).toBe("unknown");
|
|
356
356
|
});
|
|
357
357
|
test("creates new tracking entry for unknown card", () => {
|
|
358
358
|
initAutoSession(mock(async () => { }), () => makeMockClient());
|
|
@@ -406,8 +406,8 @@ describe("shutdown", () => {
|
|
|
406
406
|
startedAt: Date.now(),
|
|
407
407
|
lastActivityAt: Date.now(),
|
|
408
408
|
isExplicit: false,
|
|
409
|
-
agentIdentifier: "
|
|
410
|
-
agentName: "
|
|
409
|
+
agentIdentifier: "unknown",
|
|
410
|
+
agentName: "Unknown Agent",
|
|
411
411
|
});
|
|
412
412
|
await shutdownAllSessions();
|
|
413
413
|
expect(client.endAgentSession).toHaveBeenCalledWith(CARD_A, {
|
|
@@ -425,8 +425,8 @@ describe("shutdown", () => {
|
|
|
425
425
|
startedAt: Date.now(),
|
|
426
426
|
lastActivityAt: Date.now(),
|
|
427
427
|
isExplicit: false,
|
|
428
|
-
agentIdentifier: "
|
|
429
|
-
agentName: "
|
|
428
|
+
agentIdentifier: "unknown",
|
|
429
|
+
agentName: "Unknown Agent",
|
|
430
430
|
});
|
|
431
431
|
getActiveSessions().set(CARD_B, {
|
|
432
432
|
cardId: CARD_B,
|
|
@@ -449,8 +449,8 @@ describe("shutdown", () => {
|
|
|
449
449
|
startedAt: Date.now(),
|
|
450
450
|
lastActivityAt: Date.now(),
|
|
451
451
|
isExplicit: false,
|
|
452
|
-
agentIdentifier: "
|
|
453
|
-
agentName: "
|
|
452
|
+
agentIdentifier: "unknown",
|
|
453
|
+
agentName: "Unknown Agent",
|
|
454
454
|
});
|
|
455
455
|
// Should not throw
|
|
456
456
|
await shutdownAllSessions();
|
|
@@ -473,16 +473,16 @@ describe("shutdown", () => {
|
|
|
473
473
|
startedAt: Date.now(),
|
|
474
474
|
lastActivityAt: Date.now(),
|
|
475
475
|
isExplicit: false,
|
|
476
|
-
agentIdentifier: "
|
|
477
|
-
agentName: "
|
|
476
|
+
agentIdentifier: "unknown",
|
|
477
|
+
agentName: "Unknown Agent",
|
|
478
478
|
});
|
|
479
479
|
getActiveSessions().set(CARD_B, {
|
|
480
480
|
cardId: CARD_B,
|
|
481
481
|
startedAt: Date.now(),
|
|
482
482
|
lastActivityAt: Date.now(),
|
|
483
483
|
isExplicit: false,
|
|
484
|
-
agentIdentifier: "
|
|
485
|
-
agentName: "
|
|
484
|
+
agentIdentifier: "unknown",
|
|
485
|
+
agentName: "Unknown Agent",
|
|
486
486
|
});
|
|
487
487
|
await shutdownAllSessions();
|
|
488
488
|
// Both should be removed from tracking despite first API error
|
|
@@ -595,8 +595,8 @@ describe("edge cases", () => {
|
|
|
595
595
|
startedAt: 0,
|
|
596
596
|
lastActivityAt: 0,
|
|
597
597
|
isExplicit: false,
|
|
598
|
-
agentIdentifier: "
|
|
599
|
-
agentName: "
|
|
598
|
+
agentIdentifier: "unknown",
|
|
599
|
+
agentName: "Unknown Agent",
|
|
600
600
|
});
|
|
601
601
|
checkInactivity();
|
|
602
602
|
await new Promise((r) => setTimeout(r, 50));
|
|
@@ -615,8 +615,8 @@ describe("edge cases", () => {
|
|
|
615
615
|
startedAt: 0,
|
|
616
616
|
lastActivityAt: 0,
|
|
617
617
|
isExplicit: false,
|
|
618
|
-
agentIdentifier: "
|
|
619
|
-
agentName: "
|
|
618
|
+
agentIdentifier: "unknown",
|
|
619
|
+
agentName: "Unknown Agent",
|
|
620
620
|
});
|
|
621
621
|
checkInactivity();
|
|
622
622
|
await new Promise((r) => setTimeout(r, 50));
|
package/dist/lib/api-client.js
CHANGED
|
@@ -536,6 +536,122 @@ export class HarmonyApiClient {
|
|
|
536
536
|
async generateApiKey(name) {
|
|
537
537
|
return this.request("POST", "/api-keys", { name });
|
|
538
538
|
}
|
|
539
|
+
// ============ PROMPT GENERATION ============
|
|
540
|
+
/**
|
|
541
|
+
* Generate a prompt for a card with full memory context assembly.
|
|
542
|
+
*
|
|
543
|
+
* This is the shared entry point for prompt generation — used by the MCP
|
|
544
|
+
* server tool handler and the agent daemon. It fetches the card, assembles
|
|
545
|
+
* relevant memories, and produces a role-framed prompt.
|
|
546
|
+
*/
|
|
547
|
+
async generateCardPrompt(options) {
|
|
548
|
+
const { assembleContext, cacheManifest, generatePrompt } = await loadPromptModules();
|
|
549
|
+
// Fetch card data
|
|
550
|
+
const cardResult = await this.getCard(options.cardId);
|
|
551
|
+
const cardData = cardResult.card;
|
|
552
|
+
// Try to get column info
|
|
553
|
+
let columnData = null;
|
|
554
|
+
const projectIdForBoard = options.projectId || cardData.project_id;
|
|
555
|
+
if (projectIdForBoard) {
|
|
556
|
+
try {
|
|
557
|
+
const board = await this.getBoard(projectIdForBoard, { summary: true });
|
|
558
|
+
const column = board.columns.find((col) => col.id === cardData.column_id);
|
|
559
|
+
if (column) {
|
|
560
|
+
columnData = { name: column.name };
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
catch {
|
|
564
|
+
// Column info not available, continue without it
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const variant = options.variant || "execute";
|
|
568
|
+
// Assemble memory context
|
|
569
|
+
let assembledContextStr;
|
|
570
|
+
let assemblyId;
|
|
571
|
+
let memories;
|
|
572
|
+
try {
|
|
573
|
+
if (options.workspaceId && cardData.title) {
|
|
574
|
+
const cardLabels = (cardData.labels || []).map((l) => l.name);
|
|
575
|
+
const taskContext = [cardData.title, cardData.description || ""]
|
|
576
|
+
.filter(Boolean)
|
|
577
|
+
.join(" ");
|
|
578
|
+
const assembled = await assembleContext({
|
|
579
|
+
workspaceId: options.workspaceId,
|
|
580
|
+
projectId: options.projectId,
|
|
581
|
+
taskContext,
|
|
582
|
+
cardLabels,
|
|
583
|
+
cardId: cardData.id,
|
|
584
|
+
client: this,
|
|
585
|
+
});
|
|
586
|
+
if (assembled.context) {
|
|
587
|
+
assembledContextStr = assembled.context;
|
|
588
|
+
assemblyId = assembled.manifest.assemblyId;
|
|
589
|
+
cacheManifest(assembled.manifest);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
catch (err) {
|
|
594
|
+
// Context assembly failed, try legacy fallback
|
|
595
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
596
|
+
console.debug(`[generateCardPrompt] Context assembly failed: ${msg}`);
|
|
597
|
+
try {
|
|
598
|
+
if (options.workspaceId && cardData.title) {
|
|
599
|
+
const memoryResult = await this.searchMemoryEntities(options.workspaceId, cardData.title, {
|
|
600
|
+
project_id: options.projectId,
|
|
601
|
+
limit: 5,
|
|
602
|
+
});
|
|
603
|
+
if (memoryResult.entities?.length > 0) {
|
|
604
|
+
memories = memoryResult.entities.map((e) => ({
|
|
605
|
+
id: e.id,
|
|
606
|
+
type: e.type,
|
|
607
|
+
title: e.title,
|
|
608
|
+
content: e.content,
|
|
609
|
+
confidence: e.confidence,
|
|
610
|
+
tags: e.tags || [],
|
|
611
|
+
}));
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
catch (fallbackErr) {
|
|
616
|
+
const fallbackMsg = fallbackErr instanceof Error
|
|
617
|
+
? fallbackErr.message
|
|
618
|
+
: String(fallbackErr);
|
|
619
|
+
console.debug(`[generateCardPrompt] Memory fallback also failed: ${fallbackMsg}`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
const result = generatePrompt({
|
|
623
|
+
card: cardData,
|
|
624
|
+
column: columnData,
|
|
625
|
+
variant,
|
|
626
|
+
contextOptions: options.contextOptions,
|
|
627
|
+
customConstraints: options.customConstraints,
|
|
628
|
+
memories,
|
|
629
|
+
assembledContext: assembledContextStr,
|
|
630
|
+
assemblyId,
|
|
631
|
+
});
|
|
632
|
+
return {
|
|
633
|
+
...result,
|
|
634
|
+
cardId: cardData.id,
|
|
635
|
+
shortId: cardData.short_id,
|
|
636
|
+
title: cardData.title,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
// Cached dynamic imports for context-assembly and prompt-builder
|
|
641
|
+
let _promptModules = null;
|
|
642
|
+
async function loadPromptModules() {
|
|
643
|
+
if (!_promptModules) {
|
|
644
|
+
const [ca, pb] = await Promise.all([
|
|
645
|
+
import("./context-assembly.js"),
|
|
646
|
+
import("./prompt-builder.js"),
|
|
647
|
+
]);
|
|
648
|
+
_promptModules = {
|
|
649
|
+
assembleContext: ca.assembleContext,
|
|
650
|
+
cacheManifest: ca.cacheManifest,
|
|
651
|
+
generatePrompt: pb.generatePrompt,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
return _promptModules;
|
|
539
655
|
}
|
|
540
656
|
// Singleton instance
|
|
541
657
|
let client = null;
|
package/dist/lib/auto-session.js
CHANGED
|
@@ -4,7 +4,36 @@
|
|
|
4
4
|
* Automatically detects agent session boundaries by monitoring tool calls.
|
|
5
5
|
* Sessions auto-start when card-mutating tools are called, and auto-end
|
|
6
6
|
* after 10 minutes of inactivity or when a different card is worked on.
|
|
7
|
+
*
|
|
8
|
+
* Agent identity is resolved from the MCP client's `initialize` handshake
|
|
9
|
+
* (clientInfo.name), so "Claude Code", "Cursor", "Codex", etc. are
|
|
10
|
+
* detected automatically — no hardcoded fallback needed.
|
|
7
11
|
*/
|
|
12
|
+
/** Well-known MCP client names → human-friendly display names */
|
|
13
|
+
const CLIENT_DISPLAY_NAMES = {
|
|
14
|
+
"claude-code": "Claude Code",
|
|
15
|
+
"claude-desktop": "Claude Desktop",
|
|
16
|
+
cursor: "Cursor",
|
|
17
|
+
windsurf: "Windsurf",
|
|
18
|
+
cline: "Cline",
|
|
19
|
+
continue: "Continue",
|
|
20
|
+
"codex-cli": "OpenAI Codex",
|
|
21
|
+
zed: "Zed",
|
|
22
|
+
"gemini-cli": "Gemini CLI",
|
|
23
|
+
};
|
|
24
|
+
/** Derive a slug-style identifier from a client name */
|
|
25
|
+
function toIdentifier(name) {
|
|
26
|
+
return name.toLowerCase().replace(/\s+/g, "-");
|
|
27
|
+
}
|
|
28
|
+
/** Resolve agent identity from MCP client info */
|
|
29
|
+
export function resolveAgentIdentity(info) {
|
|
30
|
+
if (!info?.name) {
|
|
31
|
+
return { agentIdentifier: "unknown", agentName: "Unknown Agent" };
|
|
32
|
+
}
|
|
33
|
+
const key = toIdentifier(info.name);
|
|
34
|
+
const displayName = CLIENT_DISPLAY_NAMES[key] ?? info.name; // use raw name if not in map
|
|
35
|
+
return { agentIdentifier: key, agentName: displayName };
|
|
36
|
+
}
|
|
8
37
|
/** Tools that trigger auto-start of a session */
|
|
9
38
|
export const AUTO_START_TRIGGERS = new Set([
|
|
10
39
|
"harmony_generate_prompt",
|
|
@@ -21,14 +50,17 @@ const activeSessions = new Map();
|
|
|
21
50
|
let inactivityTimer = null;
|
|
22
51
|
let endCallback = null;
|
|
23
52
|
let clientGetter = null;
|
|
53
|
+
let clientInfoGetter = null;
|
|
24
54
|
/**
|
|
25
55
|
* Initialize auto-session tracking.
|
|
26
56
|
* @param callback Called when an auto-session ends (runs the learning pipeline)
|
|
27
57
|
* @param getClient Function to get the current API client
|
|
58
|
+
* @param getClientInfo Function to get MCP client identity from the initialize handshake
|
|
28
59
|
*/
|
|
29
|
-
export function initAutoSession(callback, getClient) {
|
|
60
|
+
export function initAutoSession(callback, getClient, getClientInfo) {
|
|
30
61
|
endCallback = callback;
|
|
31
62
|
clientGetter = getClient;
|
|
63
|
+
clientInfoGetter = getClientInfo ?? null;
|
|
32
64
|
if (inactivityTimer)
|
|
33
65
|
clearInterval(inactivityTimer);
|
|
34
66
|
inactivityTimer = setInterval(checkInactivity, CHECK_INTERVAL_MS);
|
|
@@ -60,11 +92,14 @@ export async function trackActivity(cardId, options) {
|
|
|
60
92
|
for (const otherCardId of toEnd) {
|
|
61
93
|
await autoEndSession(client, otherCardId, "completed");
|
|
62
94
|
}
|
|
95
|
+
// Resolve agent identity from MCP client info
|
|
96
|
+
const info = clientInfoGetter?.() ?? null;
|
|
97
|
+
const { agentIdentifier, agentName } = resolveAgentIdentity(info);
|
|
63
98
|
// Start a new auto-session
|
|
64
99
|
try {
|
|
65
100
|
await client.startAgentSession(cardId, {
|
|
66
|
-
agentIdentifier
|
|
67
|
-
agentName
|
|
101
|
+
agentIdentifier,
|
|
102
|
+
agentName,
|
|
68
103
|
status: "working",
|
|
69
104
|
});
|
|
70
105
|
}
|
|
@@ -76,8 +111,8 @@ export async function trackActivity(cardId, options) {
|
|
|
76
111
|
startedAt: now,
|
|
77
112
|
lastActivityAt: now,
|
|
78
113
|
isExplicit: false,
|
|
79
|
-
agentIdentifier
|
|
80
|
-
agentName
|
|
114
|
+
agentIdentifier,
|
|
115
|
+
agentName,
|
|
81
116
|
});
|
|
82
117
|
}
|
|
83
118
|
/**
|
|
@@ -134,6 +169,7 @@ export function destroyAutoSession() {
|
|
|
134
169
|
activeSessions.clear();
|
|
135
170
|
endCallback = null;
|
|
136
171
|
clientGetter = null;
|
|
172
|
+
clientInfoGetter = null;
|
|
137
173
|
}
|
|
138
174
|
/**
|
|
139
175
|
* Get a snapshot of active sessions (for testing/debugging).
|
package/dist/lib/cli.js
CHANGED
|
@@ -15,6 +15,11 @@ program
|
|
|
15
15
|
.command("serve")
|
|
16
16
|
.description("Start the MCP server (stdio transport)")
|
|
17
17
|
.action(async () => {
|
|
18
|
+
if (!isConfigured()) {
|
|
19
|
+
console.error("No API key configured.");
|
|
20
|
+
console.error("Run: npx @gethmy/mcp setup");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
18
23
|
await refreshSkills();
|
|
19
24
|
const server = new HarmonyMCPServer();
|
|
20
25
|
await server.run();
|
|
@@ -109,6 +114,8 @@ program
|
|
|
109
114
|
.option("-p, --project <id>", "Set project context")
|
|
110
115
|
.option("--skip-context", "Skip workspace/project selection")
|
|
111
116
|
.option("--skip-docs", "Skip project docs scaffold/verification")
|
|
117
|
+
.option("--new", "Create a new account (skip the choice prompt)")
|
|
118
|
+
.option("-n, --name <name>", "Full name (for account creation)")
|
|
112
119
|
.action(async (options) => {
|
|
113
120
|
await runSetup({
|
|
114
121
|
force: options.force,
|
|
@@ -124,6 +131,8 @@ program
|
|
|
124
131
|
projectId: options.project,
|
|
125
132
|
skipContext: options.skipContext,
|
|
126
133
|
skipDocs: options.skipDocs,
|
|
134
|
+
newAccount: options.new,
|
|
135
|
+
name: options.name,
|
|
127
136
|
});
|
|
128
137
|
});
|
|
129
138
|
program.parse();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { requestWithBearer, signupUser } from "./api-client.js";
|
|
2
|
+
import { getApiUrl } from "./config.js";
|
|
3
|
+
export async function onboardNewUser(params) {
|
|
4
|
+
const { email, password, fullName, workspaceName = `${fullName}'s Workspace`, projectName = "My First Board", template = "kanban", keyName = "mcp-agent", apiUrl = getApiUrl(), } = params;
|
|
5
|
+
// 1. Signup
|
|
6
|
+
const signupResult = await signupUser(apiUrl, {
|
|
7
|
+
email,
|
|
8
|
+
password,
|
|
9
|
+
full_name: fullName,
|
|
10
|
+
});
|
|
11
|
+
const token = signupResult.session.access_token;
|
|
12
|
+
// 2. Create workspace
|
|
13
|
+
const workspaceResult = await requestWithBearer(apiUrl, token, "POST", "/workspaces", {
|
|
14
|
+
name: workspaceName,
|
|
15
|
+
});
|
|
16
|
+
// 3. Create project
|
|
17
|
+
const projectResult = await requestWithBearer(apiUrl, token, "POST", "/projects", {
|
|
18
|
+
workspaceId: workspaceResult.workspace.id,
|
|
19
|
+
name: projectName,
|
|
20
|
+
template,
|
|
21
|
+
});
|
|
22
|
+
// 4. Generate API key
|
|
23
|
+
const keyResult = await requestWithBearer(apiUrl, token, "POST", "/api-keys", {
|
|
24
|
+
name: keyName,
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
user: signupResult.user,
|
|
28
|
+
workspace: workspaceResult.workspace,
|
|
29
|
+
project: projectResult.project,
|
|
30
|
+
columns: projectResult.columns,
|
|
31
|
+
apiKey: {
|
|
32
|
+
rawKey: keyResult.rawKey,
|
|
33
|
+
prefix: keyResult.apiKey.prefix,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|