@codeguide/core 0.0.16 → 0.0.18

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.
@@ -0,0 +1,113 @@
1
+ import { CodeGuide } from '../codeguide'
2
+ import { ConnectRepositoryRequest } from '../services/projects/project-types'
3
+ import { APIServiceConfig } from '../types'
4
+ import axios from 'axios'
5
+ import MockAdapter from 'axios-mock-adapter'
6
+
7
+ describe('CodeGuide - Repository Connection Integration', () => {
8
+ let mockAxios: MockAdapter
9
+ let codeGuide: CodeGuide
10
+ let config: APIServiceConfig
11
+
12
+ beforeEach(() => {
13
+ mockAxios = new MockAdapter(axios)
14
+ config = {
15
+ baseUrl: 'https://api.example.com',
16
+ databaseApiKey: 'sk_test123',
17
+ }
18
+ codeGuide = new CodeGuide(config, { verbose: false })
19
+ })
20
+
21
+ afterEach(() => {
22
+ mockAxios.restore()
23
+ })
24
+
25
+ describe('projects.connectRepository', () => {
26
+ const projectId = 'project-123'
27
+ const validRequest: ConnectRepositoryRequest = {
28
+ repo_url: 'https://github.com/user/my-app',
29
+ branch: 'main',
30
+ github_token: 'ghp_test123',
31
+ }
32
+
33
+ it('should connect repository successfully using projects service', async () => {
34
+ const mockResponse = {
35
+ status: 'success',
36
+ data: {
37
+ id: 'repo-connection-123',
38
+ project_id: projectId,
39
+ repo_url: validRequest.repo_url,
40
+ branch: validRequest.branch,
41
+ connection_status: 'connected' as const,
42
+ created_at: '2023-01-01T00:00:00Z',
43
+ updated_at: '2023-01-01T00:00:00Z',
44
+ },
45
+ }
46
+
47
+ mockAxios.onPost(`/projects/${projectId}/repository`, validRequest).reply(200, mockResponse)
48
+
49
+ const result = await codeGuide.projects.connectRepository(projectId, validRequest)
50
+
51
+ expect(result).toEqual(mockResponse)
52
+ })
53
+
54
+ it('should work with verbose mode enabled', async () => {
55
+ const verboseCodeGuide = new CodeGuide(config, { verbose: true })
56
+
57
+ const mockResponse = {
58
+ status: 'success',
59
+ data: {
60
+ id: 'repo-connection-123',
61
+ project_id: projectId,
62
+ repo_url: validRequest.repo_url,
63
+ branch: validRequest.branch,
64
+ connection_status: 'connected' as const,
65
+ created_at: '2023-01-01T00:00:00Z',
66
+ updated_at: '2023-01-01T00:00:00Z',
67
+ },
68
+ }
69
+
70
+ mockAxios.onPost(`/projects/${projectId}/repository`, validRequest).reply(200, mockResponse)
71
+
72
+ const result = await verboseCodeGuide.projects.connectRepository(projectId, validRequest)
73
+
74
+ expect(result).toEqual(mockResponse)
75
+ })
76
+
77
+ it('should handle validation errors through projects service', async () => {
78
+ const invalidRequest = {
79
+ repo_url: 'https://gitlab.com/user/repo', // Invalid URL
80
+ branch: 'main',
81
+ }
82
+
83
+ await expect(codeGuide.projects.connectRepository(projectId, invalidRequest))
84
+ .rejects.toThrow('Repository URL must be a valid GitHub URL')
85
+ })
86
+
87
+ it('should connect public repository without token', async () => {
88
+ const publicRepoRequest = {
89
+ repo_url: 'https://github.com/facebook/react',
90
+ branch: 'main',
91
+ }
92
+
93
+ const mockResponse = {
94
+ status: 'success',
95
+ data: {
96
+ id: 'repo-connection-456',
97
+ project_id: projectId,
98
+ repo_url: publicRepoRequest.repo_url,
99
+ branch: publicRepoRequest.branch,
100
+ connection_status: 'connected' as const,
101
+ created_at: '2023-01-01T00:00:00Z',
102
+ updated_at: '2023-01-01T00:00:00Z',
103
+ },
104
+ }
105
+
106
+ mockAxios.onPost(`/projects/${projectId}/repository`, publicRepoRequest).reply(200, mockResponse)
107
+
108
+ const result = await codeGuide.projects.connectRepository(projectId, publicRepoRequest)
109
+
110
+ expect(result).toEqual(mockResponse)
111
+ })
112
+ })
113
+ })
@@ -0,0 +1,260 @@
1
+ import { CodespaceService } from '../../../services/codespace/codespace-service'
2
+ import {
3
+ CreateCodespaceTaskRequestV2,
4
+ CreateBackgroundCodespaceTaskRequest,
5
+ ModelApiKey
6
+ } from '../../../services/codespace/codespace-types'
7
+ import { APIServiceConfig } from '../../../types'
8
+ import axios from 'axios'
9
+ import MockAdapter from 'axios-mock-adapter'
10
+
11
+ describe('CodespaceService - V2 Task Endpoints', () => {
12
+ let mockAxios: MockAdapter
13
+ let codespaceService: CodespaceService
14
+ let config: APIServiceConfig
15
+
16
+ beforeEach(() => {
17
+ mockAxios = new MockAdapter(axios)
18
+ config = {
19
+ baseUrl: 'https://api.example.com',
20
+ databaseApiKey: 'sk_test123',
21
+ }
22
+ codespaceService = new CodespaceService(config)
23
+ })
24
+
25
+ afterEach(() => {
26
+ mockAxios.restore()
27
+ })
28
+
29
+ describe('createCodespaceTaskV2', () => {
30
+ const validRequest: CreateCodespaceTaskRequestV2 = {
31
+ project_id: 'project-123',
32
+ task_description: 'Create a new authentication system',
33
+ title: 'Auth System Implementation',
34
+ execution_mode: 'implementation',
35
+ }
36
+
37
+ it('should create a codespace task successfully', async () => {
38
+ const mockResponse = {
39
+ success: true,
40
+ task_id: 'task-456',
41
+ message: 'Task created successfully',
42
+ status: 'pending',
43
+ created_at: '2023-01-01T00:00:00Z',
44
+ }
45
+
46
+ mockAxios.onPost('/codespace/task', validRequest).reply(200, mockResponse)
47
+
48
+ const result = await codespaceService.createCodespaceTaskV2(validRequest)
49
+
50
+ expect(result).toEqual(mockResponse)
51
+ })
52
+
53
+ it('should throw error for missing project_id', async () => {
54
+ const invalidRequest = {
55
+ task_description: 'Create a new feature',
56
+ }
57
+
58
+ await expect(codespaceService.createCodespaceTaskV2(invalidRequest as any))
59
+ .rejects.toThrow('project_id is required')
60
+ })
61
+
62
+ it('should throw error for missing task_description', async () => {
63
+ const invalidRequest = {
64
+ project_id: 'project-123',
65
+ }
66
+
67
+ await expect(codespaceService.createCodespaceTaskV2(invalidRequest as any))
68
+ .rejects.toThrow('task_description is required')
69
+ })
70
+
71
+ it('should set default base_branch to main', async () => {
72
+ const requestWithoutBaseBranch = {
73
+ project_id: 'project-123',
74
+ task_description: 'Create a feature',
75
+ }
76
+
77
+ const mockResponse = {
78
+ success: true,
79
+ task_id: 'task-456',
80
+ message: 'Task created successfully',
81
+ }
82
+
83
+ mockAxios.onPost('/codespace/task', expect.objectContaining({
84
+ base_branch: 'main'
85
+ })).reply(200, mockResponse)
86
+
87
+ const result = await codespaceService.createCodespaceTaskV2(requestWithoutBaseBranch)
88
+
89
+ expect(result).toEqual(mockResponse)
90
+ })
91
+
92
+ it('should validate execution_mode', async () => {
93
+ const invalidRequest = {
94
+ project_id: 'project-123',
95
+ task_description: 'Create a feature',
96
+ execution_mode: 'invalid-mode' as any,
97
+ }
98
+
99
+ await expect(codespaceService.createCodespaceTaskV2(invalidRequest))
100
+ .rejects.toThrow('execution_mode must be either "implementation" or "docs-only"')
101
+ })
102
+
103
+ it('should accept valid execution modes', async () => {
104
+ const validModes = ['implementation', 'docs-only'] as const
105
+
106
+ for (const mode of validModes) {
107
+ const request = {
108
+ project_id: 'project-123',
109
+ task_description: 'Create a feature',
110
+ execution_mode: mode,
111
+ }
112
+
113
+ const mockResponse = {
114
+ success: true,
115
+ task_id: 'task-456',
116
+ message: 'Task created successfully',
117
+ }
118
+
119
+ mockAxios.onPost('/codespace/task', request).reply(200, mockResponse)
120
+
121
+ const result = await codespaceService.createCodespaceTaskV2(request)
122
+ expect(result).toEqual(mockResponse)
123
+ }
124
+ })
125
+
126
+ it('should validate model_api_keys structure', async () => {
127
+ const invalidRequest = {
128
+ project_id: 'project-123',
129
+ task_description: 'Create a feature',
130
+ model_api_keys: 'not-an-array' as any,
131
+ }
132
+
133
+ await expect(codespaceService.createCodespaceTaskV2(invalidRequest))
134
+ .rejects.toThrow('model_api_keys must be an array')
135
+ })
136
+
137
+ it('should validate individual model_api_key entries', async () => {
138
+ const invalidRequest = {
139
+ project_id: 'project-123',
140
+ task_description: 'Create a feature',
141
+ model_api_keys: [
142
+ { model_name: '', api_key: 'key1' }, // Empty model_name
143
+ { model_name: 'model2', api_key: '' }, // Empty api_key
144
+ ],
145
+ }
146
+
147
+ await expect(codespaceService.createCodespaceTaskV2(invalidRequest))
148
+ .rejects.toThrow('Each model_api_key must have a valid model_name string')
149
+ })
150
+
151
+ it('should accept valid model_api_keys', async () => {
152
+ const validModelApiKeys: ModelApiKey[] = [
153
+ { model_name: 'gpt-4', api_key: 'sk-key1' },
154
+ { model_name: 'claude-3', api_key: 'sk-key2' },
155
+ ]
156
+
157
+ const request = {
158
+ project_id: 'project-123',
159
+ task_description: 'Create a feature',
160
+ model_api_keys: validModelApiKeys,
161
+ }
162
+
163
+ const mockResponse = {
164
+ success: true,
165
+ task_id: 'task-456',
166
+ message: 'Task created successfully',
167
+ }
168
+
169
+ mockAxios.onPost('/codespace/task', request).reply(200, mockResponse)
170
+
171
+ const result = await codespaceService.createCodespaceTaskV2(request)
172
+ expect(result).toEqual(mockResponse)
173
+ })
174
+
175
+ it('should work with all optional fields', async () => {
176
+ const fullRequest: CreateCodespaceTaskRequestV2 = {
177
+ project_id: 'project-123',
178
+ project_repository_id: 'repo-456',
179
+ task_description: 'Create comprehensive feature',
180
+ title: 'Full Feature Implementation',
181
+ branch: 'feature-branch',
182
+ working_branch: 'work-branch',
183
+ base_branch: 'develop',
184
+ docs_url: 'https://docs.example.com',
185
+ model_api_keys: [{ model_name: 'gpt-4', api_key: 'sk-key' }],
186
+ github_token: 'ghp_token123',
187
+ codespace_task_id: 'existing-task-789',
188
+ execution_mode: 'implementation',
189
+ model_name: 'gpt-4',
190
+ starter_kit_repo: 'https://github.com/example/starter-kit',
191
+ }
192
+
193
+ const mockResponse = {
194
+ success: true,
195
+ task_id: 'task-456',
196
+ message: 'Task created successfully',
197
+ status: 'pending',
198
+ created_at: '2023-01-01T00:00:00Z',
199
+ }
200
+
201
+ mockAxios.onPost('/codespace/task', fullRequest).reply(200, mockResponse)
202
+
203
+ const result = await codespaceService.createCodespaceTaskV2(fullRequest)
204
+ expect(result).toEqual(mockResponse)
205
+ })
206
+ })
207
+
208
+ describe('createBackgroundCodespaceTask', () => {
209
+ const validRequest: CreateBackgroundCodespaceTaskRequest = {
210
+ project_id: 'project-123',
211
+ task_description: 'Create background task',
212
+ execution_mode: 'docs-only',
213
+ }
214
+
215
+ it('should create a background codespace task successfully', async () => {
216
+ const mockResponse = {
217
+ success: true,
218
+ task_id: 'bg-task-456',
219
+ message: 'Background task created successfully',
220
+ status: 'queued',
221
+ created_at: '2023-01-01T00:00:00Z',
222
+ }
223
+
224
+ mockAxios.onPost('/codespace/task/background', validRequest).reply(200, mockResponse)
225
+
226
+ const result = await codespaceService.createBackgroundCodespaceTask(validRequest)
227
+
228
+ expect(result).toEqual(mockResponse)
229
+ })
230
+
231
+ it('should apply same validation as regular task creation', async () => {
232
+ const invalidRequest = {
233
+ task_description: 'Missing project_id',
234
+ }
235
+
236
+ await expect(codespaceService.createBackgroundCodespaceTask(invalidRequest as any))
237
+ .rejects.toThrow('project_id is required')
238
+ })
239
+
240
+ it('should work with docs-only mode', async () => {
241
+ const docsOnlyRequest = {
242
+ project_id: 'project-123',
243
+ task_description: 'Generate documentation',
244
+ execution_mode: 'docs-only' as const,
245
+ }
246
+
247
+ const mockResponse = {
248
+ success: true,
249
+ task_id: 'docs-task-789',
250
+ message: 'Documentation task created',
251
+ status: 'pending',
252
+ }
253
+
254
+ mockAxios.onPost('/codespace/task/background', docsOnlyRequest).reply(200, mockResponse)
255
+
256
+ const result = await codespaceService.createBackgroundCodespaceTask(docsOnlyRequest)
257
+ expect(result).toEqual(mockResponse)
258
+ })
259
+ })
260
+ })
@@ -0,0 +1,191 @@
1
+ import { ProjectService } from '../../../services/projects/project-service'
2
+ import { ConnectRepositoryRequest } from '../../../services/projects/project-types'
3
+ import { APIServiceConfig } from '../../../types'
4
+ import axios from 'axios'
5
+ import MockAdapter from 'axios-mock-adapter'
6
+
7
+ describe('ProjectService - Repository Connection', () => {
8
+ let mockAxios: MockAdapter
9
+ let projectService: ProjectService
10
+ let mockConfig: APIServiceConfig
11
+
12
+ beforeEach(() => {
13
+ mockAxios = new MockAdapter(axios)
14
+ mockConfig = {
15
+ baseUrl: 'https://api.example.com',
16
+ databaseApiKey: 'sk_test123',
17
+ }
18
+ projectService = new ProjectService(mockConfig)
19
+ })
20
+
21
+ afterEach(() => {
22
+ mockAxios.restore()
23
+ })
24
+
25
+ describe('connectRepository', () => {
26
+ const projectId = 'project-123'
27
+ const validRequest: ConnectRepositoryRequest = {
28
+ repo_url: 'https://github.com/user/my-app',
29
+ branch: 'main',
30
+ github_token: 'ghp_test123',
31
+ }
32
+
33
+ it('should successfully connect a repository with valid data', async () => {
34
+ const mockResponse = {
35
+ status: 'success',
36
+ data: {
37
+ id: 'repo-connection-123',
38
+ project_id: projectId,
39
+ repo_url: validRequest.repo_url,
40
+ branch: validRequest.branch,
41
+ connection_status: 'connected' as const,
42
+ created_at: '2023-01-01T00:00:00Z',
43
+ updated_at: '2023-01-01T00:00:00Z',
44
+ },
45
+ }
46
+
47
+ mockAxios.onPost(`/projects/${projectId}/repository`, validRequest).reply(200, mockResponse)
48
+
49
+ const result = await projectService.connectRepository(projectId, validRequest)
50
+
51
+ expect(result).toEqual(mockResponse)
52
+ })
53
+
54
+ it('should throw error for missing repository URL', async () => {
55
+ const invalidRequest = {
56
+ repo_url: '',
57
+ branch: 'main',
58
+ }
59
+
60
+ await expect(projectService.connectRepository(projectId, invalidRequest))
61
+ .rejects.toThrow('Repository URL is required')
62
+ })
63
+
64
+ it('should throw error for missing branch name', async () => {
65
+ const invalidRequest = {
66
+ repo_url: 'https://github.com/user/my-app',
67
+ branch: '',
68
+ }
69
+
70
+ await expect(projectService.connectRepository(projectId, invalidRequest))
71
+ .rejects.toThrow('Branch name is required')
72
+ })
73
+
74
+ it('should throw error for invalid GitHub URL format', async () => {
75
+ const invalidRequest = {
76
+ repo_url: 'https://gitlab.com/user/my-app',
77
+ branch: 'main',
78
+ }
79
+
80
+ await expect(projectService.connectRepository(projectId, invalidRequest))
81
+ .rejects.toThrow('Repository URL must be a valid GitHub URL')
82
+ })
83
+
84
+ it('should throw error for invalid branch name characters', async () => {
85
+ const invalidRequest = {
86
+ repo_url: 'https://github.com/user/my-app',
87
+ branch: 'main@branch',
88
+ }
89
+
90
+ await expect(projectService.connectRepository(projectId, invalidRequest))
91
+ .rejects.toThrow('Branch name contains invalid characters')
92
+ })
93
+
94
+ it('should throw error for invalid GitHub token format', async () => {
95
+ const invalidRequest = {
96
+ repo_url: 'https://github.com/user/my-app',
97
+ branch: 'main',
98
+ github_token: 'invalid-token',
99
+ }
100
+
101
+ await expect(projectService.connectRepository(projectId, invalidRequest))
102
+ .rejects.toThrow('GitHub token must be a valid personal access token')
103
+ })
104
+
105
+ it('should accept valid GitHub token formats', async () => {
106
+ const validTokens = [
107
+ 'ghp_test123',
108
+ 'gho_test123',
109
+ 'ghu_test123',
110
+ 'ghs_test123',
111
+ 'ghr_test123',
112
+ ]
113
+
114
+ for (const token of validTokens) {
115
+ const request = {
116
+ repo_url: 'https://github.com/user/my-app',
117
+ branch: 'main',
118
+ github_token: token,
119
+ }
120
+
121
+ const mockResponse = {
122
+ status: 'success',
123
+ data: {
124
+ id: 'repo-connection-123',
125
+ project_id: projectId,
126
+ repo_url: request.repo_url,
127
+ branch: request.branch,
128
+ connection_status: 'connected' as const,
129
+ created_at: '2023-01-01T00:00:00Z',
130
+ updated_at: '2023-01-01T00:00:00Z',
131
+ },
132
+ }
133
+
134
+ mockAxios.onPost(`/projects/${projectId}/repository`, request).reply(200, mockResponse)
135
+
136
+ const result = await projectService.connectRepository(projectId, request)
137
+ expect(result).toEqual(mockResponse)
138
+ }
139
+ })
140
+
141
+ it('should work without GitHub token (for public repos)', async () => {
142
+ const requestWithoutToken = {
143
+ repo_url: 'https://github.com/user/my-app',
144
+ branch: 'main',
145
+ }
146
+
147
+ const mockResponse = {
148
+ status: 'success',
149
+ data: {
150
+ id: 'repo-connection-123',
151
+ project_id: projectId,
152
+ repo_url: requestWithoutToken.repo_url,
153
+ branch: requestWithoutToken.branch,
154
+ connection_status: 'connected' as const,
155
+ created_at: '2023-01-01T00:00:00Z',
156
+ updated_at: '2023-01-01T00:00:00Z',
157
+ },
158
+ }
159
+
160
+ mockAxios.onPost(`/projects/${projectId}/repository`, requestWithoutToken).reply(200, mockResponse)
161
+
162
+ const result = await projectService.connectRepository(projectId, requestWithoutToken)
163
+ expect(result).toEqual(mockResponse)
164
+ })
165
+
166
+ it('should accept GitHub URLs with trailing slash', async () => {
167
+ const requestWithTrailingSlash = {
168
+ repo_url: 'https://github.com/user/my-app/',
169
+ branch: 'main',
170
+ }
171
+
172
+ const mockResponse = {
173
+ status: 'success',
174
+ data: {
175
+ id: 'repo-connection-123',
176
+ project_id: projectId,
177
+ repo_url: 'https://github.com/user/my-app/',
178
+ branch: 'main',
179
+ connection_status: 'connected' as const,
180
+ created_at: '2023-01-01T00:00:00Z',
181
+ updated_at: '2023-01-01T00:00:00Z',
182
+ },
183
+ }
184
+
185
+ mockAxios.onPost(`/projects/${projectId}/repository`, requestWithTrailingSlash).reply(200, mockResponse)
186
+
187
+ const result = await projectService.connectRepository(projectId, requestWithTrailingSlash)
188
+ expect(result).toEqual(mockResponse)
189
+ })
190
+ })
191
+ })
package/codeguide.ts CHANGED
@@ -87,6 +87,7 @@ export class CodeGuide {
87
87
  return this.tasks.createTaskGroupWithCodespace(request, this.codespace)
88
88
  }
89
89
 
90
+
90
91
  setOptions(options: Partial<CodeGuideOptions>): void {
91
92
  this.options = { ...this.options, ...options }
92
93
  }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export { CodeGuide } from './codeguide';
2
2
  export * from './services';
3
3
  export * from './types';
4
+ export type { ConnectRepositoryRequest, ConnectRepositoryResponse } from './services/projects/project-types';
5
+ export type { CreateCodespaceTaskRequestV2 as CreateCodespaceTaskRequest, CreateCodespaceTaskResponseV2 as CreateCodespaceTaskResponse, CreateBackgroundCodespaceTaskRequest, CreateBackgroundCodespaceTaskResponse, ModelApiKey } from './services/codespace/codespace-types';
@@ -1,6 +1,9 @@
1
1
  import { BaseService } from '../base/base-service';
2
- import { GenerateTaskTitleRequest, GenerateTaskTitleResponse, CreateCodespaceTaskRequest, CreateCodespaceTaskResponse } from './codespace-types';
2
+ import { GenerateTaskTitleRequest, GenerateTaskTitleResponse, CreateCodespaceTaskRequest, CreateCodespaceTaskResponse, CreateCodespaceTaskRequestV2, CreateCodespaceTaskResponseV2, CreateBackgroundCodespaceTaskRequest, CreateBackgroundCodespaceTaskResponse } from './codespace-types';
3
3
  export declare class CodespaceService extends BaseService {
4
4
  generateTaskTitle(request: GenerateTaskTitleRequest): Promise<GenerateTaskTitleResponse>;
5
5
  createCodespaceTask(request: CreateCodespaceTaskRequest): Promise<CreateCodespaceTaskResponse>;
6
+ createCodespaceTaskV2(request: CreateCodespaceTaskRequestV2): Promise<CreateCodespaceTaskResponseV2>;
7
+ createBackgroundCodespaceTask(request: CreateBackgroundCodespaceTaskRequest): Promise<CreateBackgroundCodespaceTaskResponse>;
8
+ private validateCodespaceTaskRequest;
6
9
  }
@@ -9,5 +9,42 @@ class CodespaceService extends base_service_1.BaseService {
9
9
  async createCodespaceTask(request) {
10
10
  return this.post('/codespace/create-task', request);
11
11
  }
12
+ async createCodespaceTaskV2(request) {
13
+ this.validateCodespaceTaskRequest(request);
14
+ return this.post('/codespace/task', request);
15
+ }
16
+ async createBackgroundCodespaceTask(request) {
17
+ this.validateCodespaceTaskRequest(request);
18
+ return this.post('/codespace/task/background', request);
19
+ }
20
+ validateCodespaceTaskRequest(request) {
21
+ if (!request.project_id) {
22
+ throw new Error('project_id is required');
23
+ }
24
+ if (!request.task_description) {
25
+ throw new Error('task_description is required');
26
+ }
27
+ if (request.execution_mode && !['implementation', 'docs-only'].includes(request.execution_mode)) {
28
+ throw new Error('execution_mode must be either "implementation" or "docs-only"');
29
+ }
30
+ // Validate model_api_keys if provided
31
+ if (request.model_api_keys) {
32
+ if (!Array.isArray(request.model_api_keys)) {
33
+ throw new Error('model_api_keys must be an array');
34
+ }
35
+ for (const key of request.model_api_keys) {
36
+ if (!key.model_name || typeof key.model_name !== 'string') {
37
+ throw new Error('Each model_api_key must have a valid model_name string');
38
+ }
39
+ if (!key.api_key || typeof key.api_key !== 'string') {
40
+ throw new Error('Each model_api_key must have a valid api_key string');
41
+ }
42
+ }
43
+ }
44
+ // Validate base_branch default
45
+ if (request.base_branch === undefined) {
46
+ request.base_branch = 'main';
47
+ }
48
+ }
12
49
  }
13
50
  exports.CodespaceService = CodespaceService;
@@ -16,3 +16,34 @@ export interface CreateCodespaceTaskResponse {
16
16
  task_id: string;
17
17
  message: string;
18
18
  }
19
+ export interface ModelApiKey {
20
+ model_name: string;
21
+ api_key: string;
22
+ }
23
+ export interface CreateCodespaceTaskRequestV2 {
24
+ project_id: string;
25
+ project_repository_id?: string;
26
+ task_description: string;
27
+ title?: string;
28
+ branch?: string;
29
+ working_branch?: string;
30
+ base_branch?: string;
31
+ docs_url?: string;
32
+ model_api_keys?: ModelApiKey[];
33
+ github_token?: string;
34
+ codespace_task_id?: string;
35
+ execution_mode?: 'implementation' | 'docs-only';
36
+ model_name?: string;
37
+ starter_kit_repo?: string;
38
+ }
39
+ export interface CreateCodespaceTaskResponseV2 {
40
+ success: boolean;
41
+ task_id: string;
42
+ message: string;
43
+ status?: string;
44
+ created_at?: string;
45
+ }
46
+ export interface CreateBackgroundCodespaceTaskRequest extends CreateCodespaceTaskRequestV2 {
47
+ }
48
+ export interface CreateBackgroundCodespaceTaskResponse extends CreateCodespaceTaskResponseV2 {
49
+ }
@@ -1,2 +1,3 @@
1
1
  export { CodespaceService } from './codespace-service';
2
2
  export * from './codespace-types';
3
+ export type { CreateCodespaceTaskRequestV2 as CreateCodespaceTaskRequest, CreateCodespaceTaskResponseV2 as CreateCodespaceTaskResponse, CreateBackgroundCodespaceTaskRequest, CreateBackgroundCodespaceTaskResponse, ModelApiKey } from './codespace-types';
@@ -1,5 +1,5 @@
1
1
  import { BaseService } from '../base/base-service';
2
- import { Project, CreateProjectRequest, UpdateProjectRequest, PaginatedProjectsRequest, PaginatedProjectsResponse, GetProjectDocumentsRequest, GetProjectDocumentsResponse } from './project-types';
2
+ import { Project, CreateProjectRequest, UpdateProjectRequest, PaginatedProjectsRequest, PaginatedProjectsResponse, GetProjectDocumentsRequest, GetProjectDocumentsResponse, ConnectRepositoryRequest, ConnectRepositoryResponse } from './project-types';
3
3
  export declare class ProjectService extends BaseService {
4
4
  getAllProjects(): Promise<Project[]>;
5
5
  getPaginatedProjects(params: PaginatedProjectsRequest): Promise<PaginatedProjectsResponse>;
@@ -11,4 +11,6 @@ export declare class ProjectService extends BaseService {
11
11
  message: string;
12
12
  }>;
13
13
  getProjectDocuments(projectId: string, params?: GetProjectDocumentsRequest): Promise<GetProjectDocumentsResponse>;
14
+ connectRepository(projectId: string, request: ConnectRepositoryRequest): Promise<ConnectRepositoryResponse>;
15
+ private validateConnectRepositoryRequest;
14
16
  }
@@ -49,5 +49,31 @@ class ProjectService extends base_service_1.BaseService {
49
49
  const url = `/projects/${projectId}/documents${queryParams.toString() ? `?${queryParams.toString()}` : ''}`;
50
50
  return this.get(url);
51
51
  }
52
+ async connectRepository(projectId, request) {
53
+ this.validateConnectRepositoryRequest(request);
54
+ const response = await this.post(`/projects/${projectId}/repository`, request);
55
+ return response;
56
+ }
57
+ validateConnectRepositoryRequest(request) {
58
+ if (!request.repo_url) {
59
+ throw new Error('Repository URL is required');
60
+ }
61
+ if (!request.branch) {
62
+ throw new Error('Branch name is required');
63
+ }
64
+ // Validate GitHub URL format
65
+ const githubUrlPattern = /^https:\/\/github\.com\/[^\/]+\/[^\/]+\/?$/;
66
+ if (!githubUrlPattern.test(request.repo_url)) {
67
+ throw new Error('Repository URL must be a valid GitHub URL (e.g., https://github.com/user/repo)');
68
+ }
69
+ // Validate branch name format (basic validation)
70
+ if (!/^[a-zA-Z0-9._-]+$/.test(request.branch)) {
71
+ throw new Error('Branch name contains invalid characters');
72
+ }
73
+ // Validate GitHub token format if provided
74
+ if (request.github_token && !request.github_token.startsWith('ghp_') && !request.github_token.startsWith('gho_') && !request.github_token.startsWith('ghu_') && !request.github_token.startsWith('ghs_') && !request.github_token.startsWith('ghr_')) {
75
+ throw new Error('GitHub token must be a valid personal access token');
76
+ }
77
+ }
52
78
  }
53
79
  exports.ProjectService = ProjectService;
@@ -95,3 +95,20 @@ export interface PaginatedProjectsResponse {
95
95
  total_pages: number;
96
96
  };
97
97
  }
98
+ export interface ConnectRepositoryRequest {
99
+ repo_url: string;
100
+ branch: string;
101
+ github_token?: string;
102
+ }
103
+ export interface ConnectRepositoryResponse {
104
+ status: string;
105
+ data: {
106
+ id: string;
107
+ project_id: string;
108
+ repo_url: string;
109
+ branch: string;
110
+ connection_status: 'pending' | 'connected' | 'failed';
111
+ created_at: string;
112
+ updated_at: string;
113
+ };
114
+ }
package/index.ts CHANGED
@@ -10,3 +10,13 @@ dotenv.config({
10
10
  export { CodeGuide } from './codeguide'
11
11
  export * from './services'
12
12
  export * from './types'
13
+
14
+ // Export commonly used types for convenience
15
+ export type { ConnectRepositoryRequest, ConnectRepositoryResponse } from './services/projects/project-types'
16
+ export type {
17
+ CreateCodespaceTaskRequestV2 as CreateCodespaceTaskRequest,
18
+ CreateCodespaceTaskResponseV2 as CreateCodespaceTaskResponse,
19
+ CreateBackgroundCodespaceTaskRequest,
20
+ CreateBackgroundCodespaceTaskResponse,
21
+ ModelApiKey
22
+ } from './services/codespace/codespace-types'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codeguide/core",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "description": "Core package for code guidance with programmatic API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -4,6 +4,10 @@ import {
4
4
  GenerateTaskTitleResponse,
5
5
  CreateCodespaceTaskRequest,
6
6
  CreateCodespaceTaskResponse,
7
+ CreateCodespaceTaskRequestV2,
8
+ CreateCodespaceTaskResponseV2,
9
+ CreateBackgroundCodespaceTaskRequest,
10
+ CreateBackgroundCodespaceTaskResponse,
7
11
  } from './codespace-types'
8
12
 
9
13
  export class CodespaceService extends BaseService {
@@ -14,4 +18,49 @@ export class CodespaceService extends BaseService {
14
18
  async createCodespaceTask(request: CreateCodespaceTaskRequest): Promise<CreateCodespaceTaskResponse> {
15
19
  return this.post<CreateCodespaceTaskResponse>('/codespace/create-task', request)
16
20
  }
21
+
22
+ async createCodespaceTaskV2(request: CreateCodespaceTaskRequestV2): Promise<CreateCodespaceTaskResponseV2> {
23
+ this.validateCodespaceTaskRequest(request)
24
+ return this.post<CreateCodespaceTaskResponseV2>('/codespace/task', request)
25
+ }
26
+
27
+ async createBackgroundCodespaceTask(request: CreateBackgroundCodespaceTaskRequest): Promise<CreateBackgroundCodespaceTaskResponse> {
28
+ this.validateCodespaceTaskRequest(request)
29
+ return this.post<CreateBackgroundCodespaceTaskResponse>('/codespace/task/background', request)
30
+ }
31
+
32
+ private validateCodespaceTaskRequest(request: CreateCodespaceTaskRequestV2): void {
33
+ if (!request.project_id) {
34
+ throw new Error('project_id is required')
35
+ }
36
+
37
+ if (!request.task_description) {
38
+ throw new Error('task_description is required')
39
+ }
40
+
41
+ if (request.execution_mode && !['implementation', 'docs-only'].includes(request.execution_mode)) {
42
+ throw new Error('execution_mode must be either "implementation" or "docs-only"')
43
+ }
44
+
45
+ // Validate model_api_keys if provided
46
+ if (request.model_api_keys) {
47
+ if (!Array.isArray(request.model_api_keys)) {
48
+ throw new Error('model_api_keys must be an array')
49
+ }
50
+
51
+ for (const key of request.model_api_keys) {
52
+ if (!key.model_name || typeof key.model_name !== 'string') {
53
+ throw new Error('Each model_api_key must have a valid model_name string')
54
+ }
55
+ if (!key.api_key || typeof key.api_key !== 'string') {
56
+ throw new Error('Each model_api_key must have a valid api_key string')
57
+ }
58
+ }
59
+ }
60
+
61
+ // Validate base_branch default
62
+ if (request.base_branch === undefined) {
63
+ request.base_branch = 'main'
64
+ }
65
+ }
17
66
  }
@@ -18,4 +18,38 @@ export interface CreateCodespaceTaskResponse {
18
18
  success: boolean
19
19
  task_id: string
20
20
  message: string
21
- }
21
+ }
22
+
23
+ export interface ModelApiKey {
24
+ model_name: string
25
+ api_key: string
26
+ }
27
+
28
+ export interface CreateCodespaceTaskRequestV2 {
29
+ project_id: string
30
+ project_repository_id?: string
31
+ task_description: string
32
+ title?: string
33
+ branch?: string
34
+ working_branch?: string
35
+ base_branch?: string
36
+ docs_url?: string
37
+ model_api_keys?: ModelApiKey[]
38
+ github_token?: string
39
+ codespace_task_id?: string
40
+ execution_mode?: 'implementation' | 'docs-only'
41
+ model_name?: string
42
+ starter_kit_repo?: string
43
+ }
44
+
45
+ export interface CreateCodespaceTaskResponseV2 {
46
+ success: boolean
47
+ task_id: string
48
+ message: string
49
+ status?: string
50
+ created_at?: string
51
+ }
52
+
53
+ export interface CreateBackgroundCodespaceTaskRequest extends CreateCodespaceTaskRequestV2 {}
54
+
55
+ export interface CreateBackgroundCodespaceTaskResponse extends CreateCodespaceTaskResponseV2 {}
@@ -1,2 +1,11 @@
1
1
  export { CodespaceService } from './codespace-service'
2
- export * from './codespace-types'
2
+ export * from './codespace-types'
3
+
4
+ // Re-export commonly used V2 types for convenience
5
+ export type {
6
+ CreateCodespaceTaskRequestV2 as CreateCodespaceTaskRequest,
7
+ CreateCodespaceTaskResponseV2 as CreateCodespaceTaskResponse,
8
+ CreateBackgroundCodespaceTaskRequest,
9
+ CreateBackgroundCodespaceTaskResponse,
10
+ ModelApiKey
11
+ } from './codespace-types'
@@ -9,6 +9,8 @@ import {
9
9
  PaginatedProjectsResponse,
10
10
  GetProjectDocumentsRequest,
11
11
  GetProjectDocumentsResponse,
12
+ ConnectRepositoryRequest,
13
+ ConnectRepositoryResponse,
12
14
  } from './project-types'
13
15
 
14
16
  export class ProjectService extends BaseService {
@@ -64,4 +66,39 @@ export class ProjectService extends BaseService {
64
66
  const url = `/projects/${projectId}/documents${queryParams.toString() ? `?${queryParams.toString()}` : ''}`
65
67
  return this.get<GetProjectDocumentsResponse>(url)
66
68
  }
69
+
70
+ async connectRepository(
71
+ projectId: string,
72
+ request: ConnectRepositoryRequest
73
+ ): Promise<ConnectRepositoryResponse> {
74
+ this.validateConnectRepositoryRequest(request)
75
+ const response = await this.post<ConnectRepositoryResponse>(`/projects/${projectId}/repository`, request)
76
+ return response
77
+ }
78
+
79
+ private validateConnectRepositoryRequest(request: ConnectRepositoryRequest): void {
80
+ if (!request.repo_url) {
81
+ throw new Error('Repository URL is required')
82
+ }
83
+
84
+ if (!request.branch) {
85
+ throw new Error('Branch name is required')
86
+ }
87
+
88
+ // Validate GitHub URL format
89
+ const githubUrlPattern = /^https:\/\/github\.com\/[^\/]+\/[^\/]+\/?$/
90
+ if (!githubUrlPattern.test(request.repo_url)) {
91
+ throw new Error('Repository URL must be a valid GitHub URL (e.g., https://github.com/user/repo)')
92
+ }
93
+
94
+ // Validate branch name format (basic validation)
95
+ if (!/^[a-zA-Z0-9._-]+$/.test(request.branch)) {
96
+ throw new Error('Branch name contains invalid characters')
97
+ }
98
+
99
+ // Validate GitHub token format if provided
100
+ if (request.github_token && !request.github_token.startsWith('ghp_') && !request.github_token.startsWith('gho_') && !request.github_token.startsWith('ghu_') && !request.github_token.startsWith('ghs_') && !request.github_token.startsWith('ghr_')) {
101
+ throw new Error('GitHub token must be a valid personal access token')
102
+ }
103
+ }
67
104
  }
@@ -106,3 +106,22 @@ export interface PaginatedProjectsResponse {
106
106
  total_pages: number
107
107
  }
108
108
  }
109
+
110
+ export interface ConnectRepositoryRequest {
111
+ repo_url: string
112
+ branch: string
113
+ github_token?: string
114
+ }
115
+
116
+ export interface ConnectRepositoryResponse {
117
+ status: string
118
+ data: {
119
+ id: string
120
+ project_id: string
121
+ repo_url: string
122
+ branch: string
123
+ connection_status: 'pending' | 'connected' | 'failed'
124
+ created_at: string
125
+ updated_at: string
126
+ }
127
+ }