@vibescope/mcp-server 0.3.0 → 0.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/api-client/blockers.d.ts +46 -0
- package/dist/api-client/blockers.js +43 -0
- package/dist/api-client/cost.d.ts +112 -0
- package/dist/api-client/cost.js +76 -0
- package/dist/api-client/decisions.d.ts +55 -0
- package/dist/api-client/decisions.js +32 -0
- package/dist/api-client/discovery.d.ts +62 -0
- package/dist/api-client/discovery.js +21 -0
- package/dist/api-client/ideas.d.ts +75 -0
- package/dist/api-client/ideas.js +36 -0
- package/dist/api-client/index.d.ts +749 -0
- package/dist/api-client/index.js +291 -0
- package/dist/api-client/project.d.ts +132 -0
- package/dist/api-client/project.js +45 -0
- package/dist/api-client/session.d.ts +163 -0
- package/dist/api-client/session.js +52 -0
- package/dist/api-client/tasks.d.ts +328 -0
- package/dist/api-client/tasks.js +132 -0
- package/dist/api-client/types.d.ts +25 -0
- package/dist/api-client/types.js +4 -0
- package/dist/api-client/worktrees.d.ts +33 -0
- package/dist/api-client/worktrees.js +26 -0
- package/dist/api-client.d.ts +9 -0
- package/dist/api-client.js +104 -25
- package/dist/cli-init.d.ts +17 -0
- package/dist/cli-init.js +445 -0
- package/dist/cli.js +0 -0
- package/dist/handlers/cloud-agents.d.ts +21 -0
- package/dist/handlers/cloud-agents.js +91 -0
- package/dist/handlers/discovery.js +7 -0
- package/dist/handlers/index.d.ts +1 -0
- package/dist/handlers/index.js +3 -0
- package/dist/handlers/session.js +3 -1
- package/dist/handlers/tasks.js +10 -12
- package/dist/handlers/types.d.ts +2 -1
- package/dist/handlers/validation.js +5 -1
- package/dist/index.js +8 -3
- package/dist/token-tracking.js +2 -2
- package/dist/tools/blockers.d.ts +13 -0
- package/dist/tools/blockers.js +119 -0
- package/dist/tools/bodies-of-work.d.ts +19 -0
- package/dist/tools/bodies-of-work.js +280 -0
- package/dist/tools/cloud-agents.d.ts +9 -0
- package/dist/tools/cloud-agents.js +67 -0
- package/dist/tools/connectors.d.ts +14 -0
- package/dist/tools/connectors.js +188 -0
- package/dist/tools/cost.d.ts +11 -0
- package/dist/tools/cost.js +108 -0
- package/dist/tools/decisions.d.ts +12 -0
- package/dist/tools/decisions.js +108 -0
- package/dist/tools/deployment.d.ts +24 -0
- package/dist/tools/deployment.js +439 -0
- package/dist/tools/discovery.d.ts +10 -0
- package/dist/tools/discovery.js +73 -0
- package/dist/tools/fallback.d.ts +11 -0
- package/dist/tools/fallback.js +108 -0
- package/dist/tools/file-checkouts.d.ts +13 -0
- package/dist/tools/file-checkouts.js +141 -0
- package/dist/tools/findings.d.ts +13 -0
- package/dist/tools/findings.js +98 -0
- package/dist/tools/git-issues.d.ts +11 -0
- package/dist/tools/git-issues.js +127 -0
- package/dist/tools/ideas.d.ts +13 -0
- package/dist/tools/ideas.js +159 -0
- package/dist/tools/index.d.ts +71 -0
- package/dist/tools/index.js +98 -0
- package/dist/tools/milestones.d.ts +12 -0
- package/dist/tools/milestones.js +115 -0
- package/dist/tools/organizations.d.ts +17 -0
- package/dist/tools/organizations.js +221 -0
- package/dist/tools/progress.d.ts +9 -0
- package/dist/tools/progress.js +70 -0
- package/dist/tools/project.d.ts +13 -0
- package/dist/tools/project.js +199 -0
- package/dist/tools/requests.d.ts +10 -0
- package/dist/tools/requests.js +65 -0
- package/dist/tools/roles.d.ts +11 -0
- package/dist/tools/roles.js +109 -0
- package/dist/tools/session.d.ts +15 -0
- package/dist/tools/session.js +178 -0
- package/dist/tools/sprints.d.ts +18 -0
- package/dist/tools/sprints.js +295 -0
- package/dist/tools/tasks.d.ts +27 -0
- package/dist/tools/tasks.js +539 -0
- package/dist/tools/types.d.ts +7 -0
- package/dist/tools/types.js +6 -0
- package/dist/tools/validation.d.ts +10 -0
- package/dist/tools/validation.js +72 -0
- package/dist/tools/worktrees.d.ts +9 -0
- package/dist/tools/worktrees.js +63 -0
- package/dist/utils.d.ts +66 -0
- package/dist/utils.js +102 -0
- package/docs/TOOLS.md +55 -2
- package/package.json +5 -3
- package/scripts/generate-docs.ts +1 -1
- package/src/api-client/blockers.ts +86 -0
- package/src/api-client/cost.ts +185 -0
- package/src/api-client/decisions.ts +87 -0
- package/src/api-client/discovery.ts +81 -0
- package/src/api-client/ideas.ts +112 -0
- package/src/api-client/index.ts +378 -0
- package/src/api-client/project.ts +179 -0
- package/src/api-client/session.ts +220 -0
- package/src/api-client/tasks.ts +450 -0
- package/src/api-client/types.ts +32 -0
- package/src/api-client/worktrees.ts +53 -0
- package/src/api-client.test.ts +136 -9
- package/src/api-client.ts +125 -27
- package/src/cli-init.ts +504 -0
- package/src/handlers/__test-utils__.ts +2 -0
- package/src/handlers/cloud-agents.ts +138 -0
- package/src/handlers/discovery.ts +7 -0
- package/src/handlers/index.ts +3 -0
- package/src/handlers/session.ts +3 -1
- package/src/handlers/tasks.ts +10 -12
- package/src/handlers/tool-categories.test.ts +1 -1
- package/src/handlers/types.ts +2 -1
- package/src/handlers/validation.ts +6 -1
- package/src/index.test.ts +2 -2
- package/src/index.ts +8 -2
- package/src/token-tracking.ts +3 -2
- package/src/tools/blockers.ts +122 -0
- package/src/tools/bodies-of-work.ts +283 -0
- package/src/tools/cloud-agents.ts +70 -0
- package/src/tools/connectors.ts +191 -0
- package/src/tools/cost.ts +111 -0
- package/src/tools/decisions.ts +111 -0
- package/src/tools/deployment.ts +442 -0
- package/src/tools/discovery.ts +76 -0
- package/src/tools/fallback.ts +111 -0
- package/src/tools/file-checkouts.ts +145 -0
- package/src/tools/findings.ts +101 -0
- package/src/tools/git-issues.ts +130 -0
- package/src/tools/ideas.ts +162 -0
- package/src/tools/index.ts +131 -0
- package/src/tools/milestones.ts +118 -0
- package/src/tools/organizations.ts +224 -0
- package/src/tools/progress.ts +73 -0
- package/src/tools/project.ts +202 -0
- package/src/tools/requests.ts +68 -0
- package/src/tools/roles.ts +112 -0
- package/src/tools/session.ts +181 -0
- package/src/tools/sprints.ts +298 -0
- package/src/tools/tasks.ts +542 -0
- package/src/tools/tools.test.ts +222 -0
- package/src/tools/types.ts +9 -0
- package/src/tools/validation.ts +75 -0
- package/src/tools/worktrees.ts +66 -0
- package/src/tools.test.ts +1 -1
- package/src/utils.test.ts +229 -0
- package/src/utils.ts +117 -0
- package/dist/tools.d.ts +0 -2
- package/dist/tools.js +0 -3602
- package/src/tools.ts +0 -3607
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decisions API Methods
|
|
3
|
+
*
|
|
4
|
+
* Methods for decision tracking:
|
|
5
|
+
* - getDecisions: List decisions for a project
|
|
6
|
+
* - getDecision: Get a single decision
|
|
7
|
+
* - logDecision: Log a new decision
|
|
8
|
+
* - deleteDecision: Delete a decision
|
|
9
|
+
* - getDecisionsStats: Get decision statistics
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ApiResponse, ProxyFn } from './types.js';
|
|
13
|
+
|
|
14
|
+
export interface DecisionsMethods {
|
|
15
|
+
getDecisions(projectId: string, options?: {
|
|
16
|
+
limit?: number;
|
|
17
|
+
offset?: number;
|
|
18
|
+
search_query?: string;
|
|
19
|
+
}): Promise<ApiResponse<{
|
|
20
|
+
decisions: Array<{
|
|
21
|
+
id: string;
|
|
22
|
+
title: string;
|
|
23
|
+
description: string;
|
|
24
|
+
rationale?: string;
|
|
25
|
+
alternatives_considered?: string[];
|
|
26
|
+
created_at: string;
|
|
27
|
+
}>;
|
|
28
|
+
total_count?: number;
|
|
29
|
+
has_more?: boolean;
|
|
30
|
+
}>>;
|
|
31
|
+
|
|
32
|
+
getDecision(decisionId: string): Promise<ApiResponse<{
|
|
33
|
+
decision: {
|
|
34
|
+
id: string;
|
|
35
|
+
title: string;
|
|
36
|
+
description: string;
|
|
37
|
+
rationale?: string;
|
|
38
|
+
alternatives_considered?: string[];
|
|
39
|
+
created_at: string;
|
|
40
|
+
};
|
|
41
|
+
}>>;
|
|
42
|
+
|
|
43
|
+
logDecision(projectId: string, params: {
|
|
44
|
+
title: string;
|
|
45
|
+
description: string;
|
|
46
|
+
rationale?: string;
|
|
47
|
+
alternatives_considered?: string[];
|
|
48
|
+
}, sessionId?: string): Promise<ApiResponse<{
|
|
49
|
+
success: boolean;
|
|
50
|
+
decision_id: string;
|
|
51
|
+
}>>;
|
|
52
|
+
|
|
53
|
+
deleteDecision(decisionId: string): Promise<ApiResponse<{
|
|
54
|
+
success: boolean;
|
|
55
|
+
}>>;
|
|
56
|
+
|
|
57
|
+
getDecisionsStats(projectId: string): Promise<ApiResponse<{
|
|
58
|
+
total: number;
|
|
59
|
+
}>>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function createDecisionsMethods(proxy: ProxyFn): DecisionsMethods {
|
|
63
|
+
return {
|
|
64
|
+
async getDecisions(projectId, options) {
|
|
65
|
+
return proxy('get_decisions', { project_id: projectId, ...options });
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
async getDecision(decisionId) {
|
|
69
|
+
return proxy('get_decision', { decision_id: decisionId });
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
async logDecision(projectId, params, sessionId) {
|
|
73
|
+
return proxy('log_decision', {
|
|
74
|
+
project_id: projectId,
|
|
75
|
+
...params
|
|
76
|
+
}, sessionId ? { session_id: sessionId } : undefined);
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
async deleteDecision(decisionId) {
|
|
80
|
+
return proxy('delete_decision', { decision_id: decisionId });
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
async getDecisionsStats(projectId) {
|
|
84
|
+
return proxy('get_decisions_stats', { project_id: projectId });
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discovery API Methods
|
|
3
|
+
*
|
|
4
|
+
* Methods for knowledge and help discovery:
|
|
5
|
+
* - queryKnowledgeBase: Query aggregated project knowledge
|
|
6
|
+
* - getHelpTopic: Get help for a specific topic
|
|
7
|
+
* - getHelpTopics: List all help topics
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ApiResponse, RequestFn } from './types.js';
|
|
11
|
+
|
|
12
|
+
export interface DiscoveryMethods {
|
|
13
|
+
queryKnowledgeBase(projectId: string, params?: {
|
|
14
|
+
scope?: 'summary' | 'detailed';
|
|
15
|
+
categories?: Array<'findings' | 'qa' | 'decisions' | 'completed_tasks' | 'blockers' | 'progress'>;
|
|
16
|
+
limit?: number;
|
|
17
|
+
search_query?: string;
|
|
18
|
+
}): Promise<ApiResponse<{
|
|
19
|
+
findings?: Array<{
|
|
20
|
+
id: string;
|
|
21
|
+
title: string;
|
|
22
|
+
category: string;
|
|
23
|
+
severity: string;
|
|
24
|
+
}>;
|
|
25
|
+
qa?: Array<{
|
|
26
|
+
id: string;
|
|
27
|
+
question: string;
|
|
28
|
+
answer?: string;
|
|
29
|
+
}>;
|
|
30
|
+
decisions?: Array<{
|
|
31
|
+
id: string;
|
|
32
|
+
title: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
}>;
|
|
35
|
+
completed_tasks?: Array<{
|
|
36
|
+
id: string;
|
|
37
|
+
title: string;
|
|
38
|
+
completed_at: string;
|
|
39
|
+
}>;
|
|
40
|
+
blockers?: Array<{
|
|
41
|
+
id: string;
|
|
42
|
+
description: string;
|
|
43
|
+
status: string;
|
|
44
|
+
}>;
|
|
45
|
+
progress?: Array<{
|
|
46
|
+
id: string;
|
|
47
|
+
note: string;
|
|
48
|
+
created_at: string;
|
|
49
|
+
}>;
|
|
50
|
+
}>>;
|
|
51
|
+
|
|
52
|
+
getHelpTopic(slug: string): Promise<ApiResponse<{
|
|
53
|
+
topic: {
|
|
54
|
+
slug: string;
|
|
55
|
+
title: string;
|
|
56
|
+
content: string;
|
|
57
|
+
};
|
|
58
|
+
}>>;
|
|
59
|
+
|
|
60
|
+
getHelpTopics(): Promise<ApiResponse<Array<{
|
|
61
|
+
slug: string;
|
|
62
|
+
title: string;
|
|
63
|
+
description?: string;
|
|
64
|
+
}>>>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function createDiscoveryMethods(request: RequestFn): DiscoveryMethods {
|
|
68
|
+
return {
|
|
69
|
+
async queryKnowledgeBase(projectId, params) {
|
|
70
|
+
return request('POST', `/api/mcp/projects/${projectId}/knowledge`, params || {});
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
async getHelpTopic(slug) {
|
|
74
|
+
return request('GET', `/api/mcp/help/${slug}`);
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
async getHelpTopics() {
|
|
78
|
+
return request('GET', '/api/mcp/help');
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ideas API Methods
|
|
3
|
+
*
|
|
4
|
+
* Methods for idea management:
|
|
5
|
+
* - getIdeas: List ideas for a project
|
|
6
|
+
* - getIdea: Get a single idea
|
|
7
|
+
* - addIdea: Create a new idea
|
|
8
|
+
* - updateIdea: Update an idea
|
|
9
|
+
* - deleteIdea: Delete an idea
|
|
10
|
+
* - convertIdeaToTask: Convert an idea to a task
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ApiResponse, ProxyFn } from './types.js';
|
|
14
|
+
|
|
15
|
+
export interface IdeasMethods {
|
|
16
|
+
getIdeas(projectId: string, params?: {
|
|
17
|
+
status?: string;
|
|
18
|
+
limit?: number;
|
|
19
|
+
offset?: number;
|
|
20
|
+
search_query?: string;
|
|
21
|
+
}): Promise<ApiResponse<{
|
|
22
|
+
ideas: Array<{
|
|
23
|
+
id: string;
|
|
24
|
+
title: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
status: string;
|
|
27
|
+
doc_url?: string;
|
|
28
|
+
created_at: string;
|
|
29
|
+
}>;
|
|
30
|
+
total_count?: number;
|
|
31
|
+
has_more?: boolean;
|
|
32
|
+
}>>;
|
|
33
|
+
|
|
34
|
+
getIdea(ideaId: string): Promise<ApiResponse<{
|
|
35
|
+
idea: {
|
|
36
|
+
id: string;
|
|
37
|
+
title: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
status: string;
|
|
40
|
+
doc_url?: string;
|
|
41
|
+
created_at: string;
|
|
42
|
+
};
|
|
43
|
+
}>>;
|
|
44
|
+
|
|
45
|
+
addIdea(projectId: string, params: {
|
|
46
|
+
title: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
status?: string;
|
|
49
|
+
}, sessionId?: string): Promise<ApiResponse<{
|
|
50
|
+
success: boolean;
|
|
51
|
+
idea_id: string;
|
|
52
|
+
}>>;
|
|
53
|
+
|
|
54
|
+
updateIdea(ideaId: string, updates: {
|
|
55
|
+
title?: string;
|
|
56
|
+
description?: string;
|
|
57
|
+
status?: string;
|
|
58
|
+
doc_url?: string;
|
|
59
|
+
}): Promise<ApiResponse<{
|
|
60
|
+
success: boolean;
|
|
61
|
+
idea_id: string;
|
|
62
|
+
}>>;
|
|
63
|
+
|
|
64
|
+
deleteIdea(ideaId: string): Promise<ApiResponse<{
|
|
65
|
+
success: boolean;
|
|
66
|
+
}>>;
|
|
67
|
+
|
|
68
|
+
convertIdeaToTask(ideaId: string, params?: {
|
|
69
|
+
priority?: number;
|
|
70
|
+
estimated_minutes?: number;
|
|
71
|
+
update_status?: boolean;
|
|
72
|
+
}): Promise<ApiResponse<{
|
|
73
|
+
success: boolean;
|
|
74
|
+
task_id?: string;
|
|
75
|
+
task_title?: string;
|
|
76
|
+
idea_id?: string;
|
|
77
|
+
idea_status?: string;
|
|
78
|
+
message?: string;
|
|
79
|
+
error?: string;
|
|
80
|
+
}>>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function createIdeasMethods(proxy: ProxyFn): IdeasMethods {
|
|
84
|
+
return {
|
|
85
|
+
async getIdeas(projectId, params) {
|
|
86
|
+
return proxy('get_ideas', { project_id: projectId, ...params });
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
async getIdea(ideaId) {
|
|
90
|
+
return proxy('get_idea', { idea_id: ideaId });
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
async addIdea(projectId, params, sessionId) {
|
|
94
|
+
return proxy('add_idea', {
|
|
95
|
+
project_id: projectId,
|
|
96
|
+
...params
|
|
97
|
+
}, sessionId ? { session_id: sessionId } : undefined);
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
async updateIdea(ideaId, updates) {
|
|
101
|
+
return proxy('update_idea', { idea_id: ideaId, ...updates });
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
async deleteIdea(ideaId) {
|
|
105
|
+
return proxy('delete_idea', { idea_id: ideaId });
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
async convertIdeaToTask(ideaId, params) {
|
|
109
|
+
return proxy('convert_idea_to_task', { idea_id: ideaId, ...params });
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vibescope API Client
|
|
3
|
+
*
|
|
4
|
+
* HTTP client for communicating with the Vibescope API.
|
|
5
|
+
* All database operations are handled server-side through these endpoints.
|
|
6
|
+
*
|
|
7
|
+
* This module composes domain-specific API methods into a unified client.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ApiClientConfig, ApiResponse, RetryConfig, RequestFn, ProxyFn } from './types.js';
|
|
11
|
+
import { createSessionMethods, type SessionMethods } from './session.js';
|
|
12
|
+
import { createProjectMethods, type ProjectMethods } from './project.js';
|
|
13
|
+
import { createBlockersMethods, type BlockersMethods } from './blockers.js';
|
|
14
|
+
import { createCostMethods, type CostMethods } from './cost.js';
|
|
15
|
+
import { createWorktreesMethods, type WorktreesMethods } from './worktrees.js';
|
|
16
|
+
import { createDiscoveryMethods, type DiscoveryMethods } from './discovery.js';
|
|
17
|
+
import { createDecisionsMethods, type DecisionsMethods } from './decisions.js';
|
|
18
|
+
import { createIdeasMethods, type IdeasMethods } from './ideas.js';
|
|
19
|
+
import { createTasksMethods, type TasksMethods } from './tasks.js';
|
|
20
|
+
|
|
21
|
+
// Re-export types
|
|
22
|
+
export type { ApiClientConfig, ApiResponse, RetryConfig, RequestFn, ProxyFn } from './types.js';
|
|
23
|
+
export type { SessionMethods } from './session.js';
|
|
24
|
+
export type { ProjectMethods } from './project.js';
|
|
25
|
+
export type { BlockersMethods } from './blockers.js';
|
|
26
|
+
export type { CostMethods } from './cost.js';
|
|
27
|
+
export type { WorktreesMethods } from './worktrees.js';
|
|
28
|
+
export type { DiscoveryMethods } from './discovery.js';
|
|
29
|
+
export type { DecisionsMethods } from './decisions.js';
|
|
30
|
+
export type { IdeasMethods } from './ideas.js';
|
|
31
|
+
export type { TasksMethods } from './tasks.js';
|
|
32
|
+
|
|
33
|
+
const DEFAULT_API_URL = 'https://vibescope.dev';
|
|
34
|
+
|
|
35
|
+
// Retry configuration defaults
|
|
36
|
+
const DEFAULT_RETRY_STATUS_CODES = [429, 503, 504];
|
|
37
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
38
|
+
const DEFAULT_BASE_DELAY_MS = 1000;
|
|
39
|
+
const DEFAULT_MAX_DELAY_MS = 30000;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Calculate delay for exponential backoff with jitter
|
|
43
|
+
*/
|
|
44
|
+
function calculateBackoffDelay(
|
|
45
|
+
attempt: number,
|
|
46
|
+
baseDelayMs: number,
|
|
47
|
+
maxDelayMs: number,
|
|
48
|
+
retryAfter?: number
|
|
49
|
+
): number {
|
|
50
|
+
if (retryAfter !== undefined && retryAfter > 0) {
|
|
51
|
+
return Math.min(retryAfter * 1000, maxDelayMs);
|
|
52
|
+
}
|
|
53
|
+
const exponentialDelay = baseDelayMs * Math.pow(2, attempt);
|
|
54
|
+
const jitter = Math.random() * 0.3 * exponentialDelay;
|
|
55
|
+
return Math.min(exponentialDelay + jitter, maxDelayMs);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Sleep for a specified duration
|
|
60
|
+
*/
|
|
61
|
+
function sleep(ms: number): Promise<void> {
|
|
62
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Combined interface for all API methods
|
|
67
|
+
*/
|
|
68
|
+
export interface VibescopeApiClientMethods
|
|
69
|
+
extends SessionMethods,
|
|
70
|
+
ProjectMethods,
|
|
71
|
+
BlockersMethods,
|
|
72
|
+
CostMethods,
|
|
73
|
+
WorktreesMethods,
|
|
74
|
+
DiscoveryMethods,
|
|
75
|
+
DecisionsMethods,
|
|
76
|
+
IdeasMethods,
|
|
77
|
+
TasksMethods {}
|
|
78
|
+
|
|
79
|
+
export class VibescopeApiClient implements VibescopeApiClientMethods {
|
|
80
|
+
private apiKey: string;
|
|
81
|
+
private baseUrl: string;
|
|
82
|
+
private retryConfig: Required<RetryConfig>;
|
|
83
|
+
|
|
84
|
+
// Domain method implementations
|
|
85
|
+
private sessionMethods: SessionMethods;
|
|
86
|
+
private projectMethods: ProjectMethods;
|
|
87
|
+
private blockersMethods: BlockersMethods;
|
|
88
|
+
private costMethods: CostMethods;
|
|
89
|
+
private worktreesMethods: WorktreesMethods;
|
|
90
|
+
private discoveryMethods: DiscoveryMethods;
|
|
91
|
+
private decisionsMethods: DecisionsMethods;
|
|
92
|
+
private ideasMethods: IdeasMethods;
|
|
93
|
+
private tasksMethods: TasksMethods;
|
|
94
|
+
|
|
95
|
+
constructor(config: ApiClientConfig) {
|
|
96
|
+
this.apiKey = config.apiKey;
|
|
97
|
+
this.baseUrl = config.baseUrl || process.env.VIBESCOPE_API_URL || DEFAULT_API_URL;
|
|
98
|
+
this.retryConfig = {
|
|
99
|
+
maxRetries: config.retry?.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
100
|
+
baseDelayMs: config.retry?.baseDelayMs ?? DEFAULT_BASE_DELAY_MS,
|
|
101
|
+
maxDelayMs: config.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS,
|
|
102
|
+
retryStatusCodes: config.retry?.retryStatusCodes ?? DEFAULT_RETRY_STATUS_CODES,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Create bound request and proxy functions for domain modules
|
|
106
|
+
const request: RequestFn = this.request.bind(this);
|
|
107
|
+
const proxy: ProxyFn = this.proxy.bind(this);
|
|
108
|
+
const getApiKey = () => this.apiKey;
|
|
109
|
+
|
|
110
|
+
// Initialize domain methods
|
|
111
|
+
this.sessionMethods = createSessionMethods(request, getApiKey);
|
|
112
|
+
this.projectMethods = createProjectMethods(request);
|
|
113
|
+
this.blockersMethods = createBlockersMethods(request);
|
|
114
|
+
this.costMethods = createCostMethods(request);
|
|
115
|
+
this.worktreesMethods = createWorktreesMethods(request);
|
|
116
|
+
this.discoveryMethods = createDiscoveryMethods(request);
|
|
117
|
+
this.decisionsMethods = createDecisionsMethods(proxy);
|
|
118
|
+
this.ideasMethods = createIdeasMethods(proxy);
|
|
119
|
+
this.tasksMethods = createTasksMethods(request, proxy);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private async request<T>(method: string, path: string, body?: unknown): Promise<ApiResponse<T>> {
|
|
123
|
+
const url = `${this.baseUrl}${path}`;
|
|
124
|
+
const { maxRetries, baseDelayMs, maxDelayMs, retryStatusCodes } = this.retryConfig;
|
|
125
|
+
let lastError: Error | null = null;
|
|
126
|
+
let lastResponse: Response | null = null;
|
|
127
|
+
|
|
128
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
129
|
+
try {
|
|
130
|
+
const response = await fetch(url, {
|
|
131
|
+
method,
|
|
132
|
+
headers: {
|
|
133
|
+
'Content-Type': 'application/json',
|
|
134
|
+
'X-API-Key': this.apiKey
|
|
135
|
+
},
|
|
136
|
+
body: body ? JSON.stringify(body) : undefined
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (retryStatusCodes.includes(response.status) && attempt < maxRetries) {
|
|
140
|
+
lastResponse = response;
|
|
141
|
+
const retryAfterHeader = response.headers.get('Retry-After');
|
|
142
|
+
let retryAfter: number | undefined;
|
|
143
|
+
if (retryAfterHeader) {
|
|
144
|
+
const parsed = parseInt(retryAfterHeader, 10);
|
|
145
|
+
if (!isNaN(parsed)) {
|
|
146
|
+
retryAfter = parsed;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const delay = calculateBackoffDelay(attempt, baseDelayMs, maxDelayMs, retryAfter);
|
|
150
|
+
await sleep(delay);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
const errorData = await response.json().catch(() => ({}));
|
|
156
|
+
return {
|
|
157
|
+
ok: false,
|
|
158
|
+
status: response.status,
|
|
159
|
+
error: errorData.error || errorData.message || `HTTP ${response.status}`
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const data = await response.json();
|
|
164
|
+
return {
|
|
165
|
+
ok: true,
|
|
166
|
+
status: response.status,
|
|
167
|
+
data
|
|
168
|
+
};
|
|
169
|
+
} catch (error) {
|
|
170
|
+
lastError = error as Error;
|
|
171
|
+
if (attempt < maxRetries) {
|
|
172
|
+
const delay = calculateBackoffDelay(attempt, baseDelayMs, maxDelayMs);
|
|
173
|
+
await sleep(delay);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (lastResponse && !lastResponse.ok) {
|
|
180
|
+
return {
|
|
181
|
+
ok: false,
|
|
182
|
+
status: lastResponse.status,
|
|
183
|
+
error: `Request failed after ${maxRetries + 1} attempts with status ${lastResponse.status}`
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
status: 0,
|
|
190
|
+
error: lastError?.message || 'Request failed after all retries'
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Generic proxy method for operations that go through the MCP proxy endpoint
|
|
196
|
+
*/
|
|
197
|
+
async proxy<T>(operation: string, args: Record<string, unknown>, sessionContext?: {
|
|
198
|
+
session_id?: string;
|
|
199
|
+
project_id?: string;
|
|
200
|
+
}): Promise<ApiResponse<T>> {
|
|
201
|
+
return this.request('POST', '/api/mcp/proxy', {
|
|
202
|
+
operation,
|
|
203
|
+
args,
|
|
204
|
+
...sessionContext
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ============================================================================
|
|
209
|
+
// Session methods (delegated)
|
|
210
|
+
// ============================================================================
|
|
211
|
+
validateAuth = () => this.sessionMethods.validateAuth();
|
|
212
|
+
startSession = (params: Parameters<SessionMethods['startSession']>[0]) => this.sessionMethods.startSession(params);
|
|
213
|
+
heartbeat = (sessionId: string, options?: Parameters<SessionMethods['heartbeat']>[1]) => this.sessionMethods.heartbeat(sessionId, options);
|
|
214
|
+
endSession = (sessionId: string) => this.sessionMethods.endSession(sessionId);
|
|
215
|
+
signalIdle = (sessionId: string) => this.sessionMethods.signalIdle(sessionId);
|
|
216
|
+
syncSession = (sessionId: string, params?: Parameters<SessionMethods['syncSession']>[1]) => this.sessionMethods.syncSession(sessionId, params);
|
|
217
|
+
confirmAgentSetup = (projectId: string, agentType: string) => this.sessionMethods.confirmAgentSetup(projectId, agentType);
|
|
218
|
+
|
|
219
|
+
// ============================================================================
|
|
220
|
+
// Project methods (delegated)
|
|
221
|
+
// ============================================================================
|
|
222
|
+
listProjects = () => this.projectMethods.listProjects();
|
|
223
|
+
createProject = (params: Parameters<ProjectMethods['createProject']>[0]) => this.projectMethods.createProject(params);
|
|
224
|
+
getProject = (projectId: string, gitUrl?: string) => this.projectMethods.getProject(projectId, gitUrl);
|
|
225
|
+
updateProject = (projectId: string, updates: Parameters<ProjectMethods['updateProject']>[1]) => this.projectMethods.updateProject(projectId, updates);
|
|
226
|
+
getGitWorkflow = (projectId: string, taskId?: string) => this.projectMethods.getGitWorkflow(projectId, taskId);
|
|
227
|
+
updateProjectReadme = (projectId: string, readmeContent: string) => this.projectMethods.updateProjectReadme(projectId, readmeContent);
|
|
228
|
+
getProjectSummary = (projectId: string) => this.projectMethods.getProjectSummary(projectId);
|
|
229
|
+
|
|
230
|
+
// ============================================================================
|
|
231
|
+
// Blockers methods (delegated)
|
|
232
|
+
// ============================================================================
|
|
233
|
+
getBlockers = (projectId: string, params?: Parameters<BlockersMethods['getBlockers']>[1]) => this.blockersMethods.getBlockers(projectId, params);
|
|
234
|
+
addBlocker = (projectId: string, description: string, sessionId?: string) => this.blockersMethods.addBlocker(projectId, description, sessionId);
|
|
235
|
+
resolveBlocker = (blockerId: string, resolutionNote?: string) => this.blockersMethods.resolveBlocker(blockerId, resolutionNote);
|
|
236
|
+
deleteBlocker = (blockerId: string) => this.blockersMethods.deleteBlocker(blockerId);
|
|
237
|
+
getBlockersStats = (projectId: string) => this.blockersMethods.getBlockersStats(projectId);
|
|
238
|
+
|
|
239
|
+
// ============================================================================
|
|
240
|
+
// Cost methods (delegated)
|
|
241
|
+
// ============================================================================
|
|
242
|
+
getCostSummary = (projectId: string, params?: Parameters<CostMethods['getCostSummary']>[1]) => this.costMethods.getCostSummary(projectId, params);
|
|
243
|
+
getCostAlerts = () => this.costMethods.getCostAlerts();
|
|
244
|
+
addCostAlert = (params: Parameters<CostMethods['addCostAlert']>[0]) => this.costMethods.addCostAlert(params);
|
|
245
|
+
updateCostAlert = (alertId: string, updates: Parameters<CostMethods['updateCostAlert']>[1]) => this.costMethods.updateCostAlert(alertId, updates);
|
|
246
|
+
deleteCostAlert = (alertId: string) => this.costMethods.deleteCostAlert(alertId);
|
|
247
|
+
getTaskCosts = (projectId: string, limit?: number) => this.costMethods.getTaskCosts(projectId, limit);
|
|
248
|
+
getBodyOfWorkCosts = (params: Parameters<CostMethods['getBodyOfWorkCosts']>[0]) => this.costMethods.getBodyOfWorkCosts(params);
|
|
249
|
+
getSprintCosts = (params: Parameters<CostMethods['getSprintCosts']>[0]) => this.costMethods.getSprintCosts(params);
|
|
250
|
+
getTokenUsage = () => this.costMethods.getTokenUsage();
|
|
251
|
+
reportTokenUsage = (sessionId: string, params: Parameters<CostMethods['reportTokenUsage']>[1]) => this.costMethods.reportTokenUsage(sessionId, params);
|
|
252
|
+
|
|
253
|
+
// ============================================================================
|
|
254
|
+
// Worktrees methods (delegated)
|
|
255
|
+
// ============================================================================
|
|
256
|
+
getStaleWorktrees = (projectId: string, params?: Parameters<WorktreesMethods['getStaleWorktrees']>[1]) => this.worktreesMethods.getStaleWorktrees(projectId, params);
|
|
257
|
+
clearWorktreePath = (taskId: string) => this.worktreesMethods.clearWorktreePath(taskId);
|
|
258
|
+
|
|
259
|
+
// ============================================================================
|
|
260
|
+
// Discovery methods (delegated)
|
|
261
|
+
// ============================================================================
|
|
262
|
+
queryKnowledgeBase = (projectId: string, params?: Parameters<DiscoveryMethods['queryKnowledgeBase']>[1]) => this.discoveryMethods.queryKnowledgeBase(projectId, params);
|
|
263
|
+
getHelpTopic = (slug: string) => this.discoveryMethods.getHelpTopic(slug);
|
|
264
|
+
getHelpTopics = () => this.discoveryMethods.getHelpTopics();
|
|
265
|
+
|
|
266
|
+
// ============================================================================
|
|
267
|
+
// Decisions methods (delegated)
|
|
268
|
+
// ============================================================================
|
|
269
|
+
getDecisions = (projectId: string, options?: Parameters<DecisionsMethods['getDecisions']>[1]) => this.decisionsMethods.getDecisions(projectId, options);
|
|
270
|
+
getDecision = (decisionId: string) => this.decisionsMethods.getDecision(decisionId);
|
|
271
|
+
logDecision = (projectId: string, params: Parameters<DecisionsMethods['logDecision']>[1], sessionId?: string) => this.decisionsMethods.logDecision(projectId, params, sessionId);
|
|
272
|
+
deleteDecision = (decisionId: string) => this.decisionsMethods.deleteDecision(decisionId);
|
|
273
|
+
getDecisionsStats = (projectId: string) => this.decisionsMethods.getDecisionsStats(projectId);
|
|
274
|
+
|
|
275
|
+
// ============================================================================
|
|
276
|
+
// Ideas methods (delegated)
|
|
277
|
+
// ============================================================================
|
|
278
|
+
getIdeas = (projectId: string, params?: Parameters<IdeasMethods['getIdeas']>[1]) => this.ideasMethods.getIdeas(projectId, params);
|
|
279
|
+
getIdea = (ideaId: string) => this.ideasMethods.getIdea(ideaId);
|
|
280
|
+
addIdea = (projectId: string, params: Parameters<IdeasMethods['addIdea']>[1], sessionId?: string) => this.ideasMethods.addIdea(projectId, params, sessionId);
|
|
281
|
+
updateIdea = (ideaId: string, updates: Parameters<IdeasMethods['updateIdea']>[1]) => this.ideasMethods.updateIdea(ideaId, updates);
|
|
282
|
+
deleteIdea = (ideaId: string) => this.ideasMethods.deleteIdea(ideaId);
|
|
283
|
+
convertIdeaToTask = (ideaId: string, params?: Parameters<IdeasMethods['convertIdeaToTask']>[1]) => this.ideasMethods.convertIdeaToTask(ideaId, params);
|
|
284
|
+
|
|
285
|
+
// ============================================================================
|
|
286
|
+
// Tasks methods (delegated)
|
|
287
|
+
// ============================================================================
|
|
288
|
+
getTasks = (projectId: string, params?: Parameters<TasksMethods['getTasks']>[1]) => this.tasksMethods.getTasks(projectId, params);
|
|
289
|
+
createTask = (projectId: string, params: Parameters<TasksMethods['createTask']>[1]) => this.tasksMethods.createTask(projectId, params);
|
|
290
|
+
getNextTask = (projectId: string, sessionId?: string) => this.tasksMethods.getNextTask(projectId, sessionId);
|
|
291
|
+
getTask = (taskId: string) => this.tasksMethods.getTask(taskId);
|
|
292
|
+
getTaskById = (taskId: string, params?: Parameters<TasksMethods['getTaskById']>[1]) => this.tasksMethods.getTaskById(taskId, params);
|
|
293
|
+
searchTasks = (projectId: string, params: Parameters<TasksMethods['searchTasks']>[1]) => this.tasksMethods.searchTasks(projectId, params);
|
|
294
|
+
getTasksByPriority = (projectId: string, params?: Parameters<TasksMethods['getTasksByPriority']>[1]) => this.tasksMethods.getTasksByPriority(projectId, params);
|
|
295
|
+
getRecentTasks = (projectId: string, params?: Parameters<TasksMethods['getRecentTasks']>[1]) => this.tasksMethods.getRecentTasks(projectId, params);
|
|
296
|
+
getTaskStats = (projectId: string) => this.tasksMethods.getTaskStats(projectId);
|
|
297
|
+
updateTask = (taskId: string, updates: Parameters<TasksMethods['updateTask']>[1]) => this.tasksMethods.updateTask(taskId, updates);
|
|
298
|
+
completeTask = (taskId: string, params: Parameters<TasksMethods['completeTask']>[1]) => this.tasksMethods.completeTask(taskId, params);
|
|
299
|
+
deleteTask = (taskId: string) => this.tasksMethods.deleteTask(taskId);
|
|
300
|
+
releaseTask = (taskId: string, params?: Parameters<TasksMethods['releaseTask']>[1]) => this.tasksMethods.releaseTask(taskId, params);
|
|
301
|
+
cancelTask = (taskId: string, params?: Parameters<TasksMethods['cancelTask']>[1]) => this.tasksMethods.cancelTask(taskId, params);
|
|
302
|
+
addTaskReference = (taskId: string, url: string, label?: string) => this.tasksMethods.addTaskReference(taskId, url, label);
|
|
303
|
+
removeTaskReference = (taskId: string, url: string) => this.tasksMethods.removeTaskReference(taskId, url);
|
|
304
|
+
batchUpdateTasks = (updates: Parameters<TasksMethods['batchUpdateTasks']>[0]) => this.tasksMethods.batchUpdateTasks(updates);
|
|
305
|
+
batchCompleteTasks = (completions: Parameters<TasksMethods['batchCompleteTasks']>[0]) => this.tasksMethods.batchCompleteTasks(completions);
|
|
306
|
+
|
|
307
|
+
// ============================================================================
|
|
308
|
+
// TODO: Additional methods to be migrated from original api-client.ts
|
|
309
|
+
// The following domains need to be split into separate files:
|
|
310
|
+
// - findings.ts (getFindings, addFinding, etc.)
|
|
311
|
+
// - milestones.ts (getMilestones, addMilestone, etc.)
|
|
312
|
+
// - requests.ts (getPendingRequests, answerQuestion, etc.)
|
|
313
|
+
// - validation.ts (getTasksAwaitingValidation, validateTask, etc.)
|
|
314
|
+
// - fallback.ts (startFallbackActivity, stopFallbackActivity)
|
|
315
|
+
// - deployment.ts (requestDeployment, startDeployment, etc.)
|
|
316
|
+
// - organizations.ts (listOrganizations, etc.)
|
|
317
|
+
// - bodies-of-work.ts (createBodyOfWork, etc.)
|
|
318
|
+
// - git-issues.ts (addGitIssue, etc.)
|
|
319
|
+
// - file-checkouts.ts (checkoutFile, checkinFile, etc.)
|
|
320
|
+
// - connectors.ts (getConnectors, addConnector, etc.)
|
|
321
|
+
// - sprints.ts (getSprints, createSprint, etc.)
|
|
322
|
+
// - subtasks.ts (addSubtask, getSubtasks)
|
|
323
|
+
// - progress.ts (logProgress, getActivityHistory, etc.)
|
|
324
|
+
// ============================================================================
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Singleton instance
|
|
328
|
+
let apiClient: VibescopeApiClient | null = null;
|
|
329
|
+
|
|
330
|
+
export function getApiClient(): VibescopeApiClient {
|
|
331
|
+
if (!apiClient) {
|
|
332
|
+
const apiKey = resolveApiKeyFromSources();
|
|
333
|
+
apiClient = new VibescopeApiClient({ apiKey });
|
|
334
|
+
}
|
|
335
|
+
return apiClient;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Resolve API key from multiple sources in priority order:
|
|
340
|
+
* 1. VIBESCOPE_API_KEY env var (CI/cloud agents)
|
|
341
|
+
* 2. ~/.vibescope/credentials.json (local dev)
|
|
342
|
+
* 3. Error with helpful message
|
|
343
|
+
*/
|
|
344
|
+
function resolveApiKeyFromSources(): string {
|
|
345
|
+
// 1. Environment variable
|
|
346
|
+
if (process.env.VIBESCOPE_API_KEY) {
|
|
347
|
+
return process.env.VIBESCOPE_API_KEY;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// 2. Credentials file
|
|
351
|
+
try {
|
|
352
|
+
const { existsSync, readFileSync } = require('node:fs');
|
|
353
|
+
const { homedir } = require('node:os');
|
|
354
|
+
const { join } = require('node:path');
|
|
355
|
+
const credPath = join(homedir(), '.vibescope', 'credentials.json');
|
|
356
|
+
if (existsSync(credPath)) {
|
|
357
|
+
const data = JSON.parse(readFileSync(credPath, 'utf-8'));
|
|
358
|
+
if (data.apiKey) {
|
|
359
|
+
return data.apiKey;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
} catch {
|
|
363
|
+
// ignore read errors
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// 3. Not found
|
|
367
|
+
throw new Error(
|
|
368
|
+
'Vibescope API key not found.\n\n' +
|
|
369
|
+
'Set it up with:\n' +
|
|
370
|
+
' npx vibescope init\n\n' +
|
|
371
|
+
'Or set the VIBESCOPE_API_KEY environment variable.'
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function initApiClient(config: ApiClientConfig): VibescopeApiClient {
|
|
376
|
+
apiClient = new VibescopeApiClient(config);
|
|
377
|
+
return apiClient;
|
|
378
|
+
}
|