claudedesk 1.0.0

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.
Files changed (182) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +431 -0
  3. package/config/repos.example.json +128 -0
  4. package/config/settings.example.json +64 -0
  5. package/config/skills/code-review.md +76 -0
  6. package/config/skills/full-check.md +26 -0
  7. package/config/skills/lint-fix.md +23 -0
  8. package/dist/api/agent-routes.d.ts +2 -0
  9. package/dist/api/agent-routes.d.ts.map +1 -0
  10. package/dist/api/agent-routes.js +251 -0
  11. package/dist/api/agent-routes.js.map +1 -0
  12. package/dist/api/app-routes.d.ts +2 -0
  13. package/dist/api/app-routes.d.ts.map +1 -0
  14. package/dist/api/app-routes.js +150 -0
  15. package/dist/api/app-routes.js.map +1 -0
  16. package/dist/api/docker-routes.d.ts +2 -0
  17. package/dist/api/docker-routes.d.ts.map +1 -0
  18. package/dist/api/docker-routes.js +167 -0
  19. package/dist/api/docker-routes.js.map +1 -0
  20. package/dist/api/middleware.d.ts +6 -0
  21. package/dist/api/middleware.d.ts.map +1 -0
  22. package/dist/api/middleware.js +293 -0
  23. package/dist/api/middleware.js.map +1 -0
  24. package/dist/api/pin-auth.d.ts +65 -0
  25. package/dist/api/pin-auth.d.ts.map +1 -0
  26. package/dist/api/pin-auth.js +218 -0
  27. package/dist/api/pin-auth.js.map +1 -0
  28. package/dist/api/routes.d.ts +2 -0
  29. package/dist/api/routes.d.ts.map +1 -0
  30. package/dist/api/routes.js +473 -0
  31. package/dist/api/routes.js.map +1 -0
  32. package/dist/api/settings-routes.d.ts +2 -0
  33. package/dist/api/settings-routes.d.ts.map +1 -0
  34. package/dist/api/settings-routes.js +570 -0
  35. package/dist/api/settings-routes.js.map +1 -0
  36. package/dist/api/skill-routes.d.ts +2 -0
  37. package/dist/api/skill-routes.d.ts.map +1 -0
  38. package/dist/api/skill-routes.js +88 -0
  39. package/dist/api/skill-routes.js.map +1 -0
  40. package/dist/api/terminal-routes.d.ts +2 -0
  41. package/dist/api/terminal-routes.d.ts.map +1 -0
  42. package/dist/api/terminal-routes.js +3524 -0
  43. package/dist/api/terminal-routes.js.map +1 -0
  44. package/dist/api/tunnel-routes.d.ts +2 -0
  45. package/dist/api/tunnel-routes.d.ts.map +1 -0
  46. package/dist/api/tunnel-routes.js +196 -0
  47. package/dist/api/tunnel-routes.js.map +1 -0
  48. package/dist/api/workspace-routes.d.ts +3 -0
  49. package/dist/api/workspace-routes.d.ts.map +1 -0
  50. package/dist/api/workspace-routes.js +649 -0
  51. package/dist/api/workspace-routes.js.map +1 -0
  52. package/dist/cli.d.ts +3 -0
  53. package/dist/cli.d.ts.map +1 -0
  54. package/dist/cli.js +276 -0
  55. package/dist/cli.js.map +1 -0
  56. package/dist/client/assets/index-B4r0njGe.js +780 -0
  57. package/dist/client/assets/index-CY_9MyE0.css +1 -0
  58. package/dist/client/favicon.svg +5 -0
  59. package/dist/client/icons/icon-192.svg +5 -0
  60. package/dist/client/icons/icon-512.svg +5 -0
  61. package/dist/client/icons/logo-with-message.png +0 -0
  62. package/dist/client/icons/logo.png +0 -0
  63. package/dist/client/index.html +25 -0
  64. package/dist/client/manifest.json +62 -0
  65. package/dist/client/sw.js +243 -0
  66. package/dist/config/agent-usage.d.ts +34 -0
  67. package/dist/config/agent-usage.d.ts.map +1 -0
  68. package/dist/config/agent-usage.js +87 -0
  69. package/dist/config/agent-usage.js.map +1 -0
  70. package/dist/config/repos.d.ts +34 -0
  71. package/dist/config/repos.d.ts.map +1 -0
  72. package/dist/config/repos.js +412 -0
  73. package/dist/config/repos.js.map +1 -0
  74. package/dist/config/settings.d.ts +634 -0
  75. package/dist/config/settings.d.ts.map +1 -0
  76. package/dist/config/settings.js +459 -0
  77. package/dist/config/settings.js.map +1 -0
  78. package/dist/config/skills.d.ts +18 -0
  79. package/dist/config/skills.d.ts.map +1 -0
  80. package/dist/config/skills.js +174 -0
  81. package/dist/config/skills.js.map +1 -0
  82. package/dist/config/workspaces.d.ts +961 -0
  83. package/dist/config/workspaces.d.ts.map +1 -0
  84. package/dist/config/workspaces.js +482 -0
  85. package/dist/config/workspaces.js.map +1 -0
  86. package/dist/core/app-manager.d.ts +85 -0
  87. package/dist/core/app-manager.d.ts.map +1 -0
  88. package/dist/core/app-manager.js +447 -0
  89. package/dist/core/app-manager.js.map +1 -0
  90. package/dist/core/claude-invoker.d.ts +49 -0
  91. package/dist/core/claude-invoker.d.ts.map +1 -0
  92. package/dist/core/claude-invoker.js +583 -0
  93. package/dist/core/claude-invoker.js.map +1 -0
  94. package/dist/core/claude-session-reader.d.ts +25 -0
  95. package/dist/core/claude-session-reader.d.ts.map +1 -0
  96. package/dist/core/claude-session-reader.js +184 -0
  97. package/dist/core/claude-session-reader.js.map +1 -0
  98. package/dist/core/claude-usage-query.d.ts +78 -0
  99. package/dist/core/claude-usage-query.d.ts.map +1 -0
  100. package/dist/core/claude-usage-query.js +294 -0
  101. package/dist/core/claude-usage-query.js.map +1 -0
  102. package/dist/core/git-credential-helper.d.ts +57 -0
  103. package/dist/core/git-credential-helper.d.ts.map +1 -0
  104. package/dist/core/git-credential-helper.js +176 -0
  105. package/dist/core/git-credential-helper.js.map +1 -0
  106. package/dist/core/git-sandbox.d.ts +135 -0
  107. package/dist/core/git-sandbox.d.ts.map +1 -0
  108. package/dist/core/git-sandbox.js +907 -0
  109. package/dist/core/git-sandbox.js.map +1 -0
  110. package/dist/core/github-integration.d.ts +66 -0
  111. package/dist/core/github-integration.d.ts.map +1 -0
  112. package/dist/core/github-integration.js +350 -0
  113. package/dist/core/github-integration.js.map +1 -0
  114. package/dist/core/github-oauth.d.ts +88 -0
  115. package/dist/core/github-oauth.d.ts.map +1 -0
  116. package/dist/core/github-oauth.js +244 -0
  117. package/dist/core/github-oauth.js.map +1 -0
  118. package/dist/core/gitlab-integration.d.ts +66 -0
  119. package/dist/core/gitlab-integration.d.ts.map +1 -0
  120. package/dist/core/gitlab-integration.js +353 -0
  121. package/dist/core/gitlab-integration.js.map +1 -0
  122. package/dist/core/gitlab-oauth.d.ts +100 -0
  123. package/dist/core/gitlab-oauth.d.ts.map +1 -0
  124. package/dist/core/gitlab-oauth.js +366 -0
  125. package/dist/core/gitlab-oauth.js.map +1 -0
  126. package/dist/core/insights-extractor.d.ts +68 -0
  127. package/dist/core/insights-extractor.d.ts.map +1 -0
  128. package/dist/core/insights-extractor.js +402 -0
  129. package/dist/core/insights-extractor.js.map +1 -0
  130. package/dist/core/logger.d.ts +27 -0
  131. package/dist/core/logger.d.ts.map +1 -0
  132. package/dist/core/logger.js +70 -0
  133. package/dist/core/logger.js.map +1 -0
  134. package/dist/core/process-runner.d.ts +27 -0
  135. package/dist/core/process-runner.d.ts.map +1 -0
  136. package/dist/core/process-runner.js +147 -0
  137. package/dist/core/process-runner.js.map +1 -0
  138. package/dist/core/project-detector.d.ts +30 -0
  139. package/dist/core/project-detector.d.ts.map +1 -0
  140. package/dist/core/project-detector.js +482 -0
  141. package/dist/core/project-detector.js.map +1 -0
  142. package/dist/core/qr-generator.d.ts +18 -0
  143. package/dist/core/qr-generator.d.ts.map +1 -0
  144. package/dist/core/qr-generator.js +61 -0
  145. package/dist/core/qr-generator.js.map +1 -0
  146. package/dist/core/remote-tunnel-manager.d.ts +59 -0
  147. package/dist/core/remote-tunnel-manager.d.ts.map +1 -0
  148. package/dist/core/remote-tunnel-manager.js +235 -0
  149. package/dist/core/remote-tunnel-manager.js.map +1 -0
  150. package/dist/core/shared-docker-manager.d.ts +41 -0
  151. package/dist/core/shared-docker-manager.d.ts.map +1 -0
  152. package/dist/core/shared-docker-manager.js +409 -0
  153. package/dist/core/shared-docker-manager.js.map +1 -0
  154. package/dist/core/skill-executor.d.ts +25 -0
  155. package/dist/core/skill-executor.d.ts.map +1 -0
  156. package/dist/core/skill-executor.js +171 -0
  157. package/dist/core/skill-executor.js.map +1 -0
  158. package/dist/core/terminal-session.d.ts +149 -0
  159. package/dist/core/terminal-session.d.ts.map +1 -0
  160. package/dist/core/terminal-session.js +2340 -0
  161. package/dist/core/terminal-session.js.map +1 -0
  162. package/dist/core/tunnel-manager.d.ts +35 -0
  163. package/dist/core/tunnel-manager.d.ts.map +1 -0
  164. package/dist/core/tunnel-manager.js +137 -0
  165. package/dist/core/tunnel-manager.js.map +1 -0
  166. package/dist/core/usage-manager.d.ts +57 -0
  167. package/dist/core/usage-manager.d.ts.map +1 -0
  168. package/dist/core/usage-manager.js +363 -0
  169. package/dist/core/usage-manager.js.map +1 -0
  170. package/dist/core/ws-manager.d.ts +39 -0
  171. package/dist/core/ws-manager.d.ts.map +1 -0
  172. package/dist/core/ws-manager.js +190 -0
  173. package/dist/core/ws-manager.js.map +1 -0
  174. package/dist/index.d.ts +7 -0
  175. package/dist/index.d.ts.map +1 -0
  176. package/dist/index.js +229 -0
  177. package/dist/index.js.map +1 -0
  178. package/dist/types.d.ts +868 -0
  179. package/dist/types.d.ts.map +1 -0
  180. package/dist/types.js +119 -0
  181. package/dist/types.js.map +1 -0
  182. package/package.json +96 -0
@@ -0,0 +1,244 @@
1
+ /**
2
+ * GitHub OAuth Device Flow Implementation
3
+ *
4
+ * The device flow allows CLI/desktop apps to authenticate without a callback URL:
5
+ * 1. Request a device code from GitHub
6
+ * 2. User goes to github.com/login/device and enters the code
7
+ * 3. App polls GitHub until user completes authorization
8
+ * 4. GitHub returns access token
9
+ *
10
+ * See: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow
11
+ */
12
+ // Store active device flow sessions (device_code -> pending state)
13
+ const activeSessions = new Map();
14
+ export class GitHubDeviceAuth {
15
+ clientId;
16
+ constructor(clientId) {
17
+ this.clientId = clientId;
18
+ }
19
+ /**
20
+ * Step 1: Request a device code from GitHub
21
+ * User will need to visit the verification URI and enter the user code
22
+ */
23
+ async requestDeviceCode(scope = 'repo') {
24
+ const response = await fetch('https://github.com/login/device/code', {
25
+ method: 'POST',
26
+ headers: {
27
+ 'Accept': 'application/json',
28
+ 'Content-Type': 'application/json',
29
+ },
30
+ body: JSON.stringify({
31
+ client_id: this.clientId,
32
+ scope,
33
+ }),
34
+ });
35
+ if (!response.ok) {
36
+ const error = await response.text();
37
+ throw new Error(`Failed to request device code: ${error}`);
38
+ }
39
+ const data = await response.json();
40
+ const result = {
41
+ deviceCode: data.device_code,
42
+ userCode: data.user_code,
43
+ verificationUri: data.verification_uri,
44
+ expiresIn: data.expires_in,
45
+ interval: data.interval,
46
+ };
47
+ // Store session for polling
48
+ activeSessions.set(result.deviceCode, {
49
+ deviceCode: result.deviceCode,
50
+ interval: result.interval,
51
+ expiresAt: Date.now() + (result.expiresIn * 1000),
52
+ clientId: this.clientId,
53
+ });
54
+ return result;
55
+ }
56
+ /**
57
+ * Step 2: Poll for access token
58
+ * Call this periodically (respecting the interval) until authorization completes
59
+ */
60
+ async pollForToken(deviceCode) {
61
+ const session = activeSessions.get(deviceCode);
62
+ if (!session) {
63
+ return { status: 'error', error: 'Device code session not found' };
64
+ }
65
+ // Check if expired
66
+ if (Date.now() > session.expiresAt) {
67
+ activeSessions.delete(deviceCode);
68
+ return { status: 'expired', error: 'Device code has expired' };
69
+ }
70
+ try {
71
+ const response = await fetch('https://github.com/login/oauth/access_token', {
72
+ method: 'POST',
73
+ headers: {
74
+ 'Accept': 'application/json',
75
+ 'Content-Type': 'application/json',
76
+ },
77
+ body: JSON.stringify({
78
+ client_id: session.clientId,
79
+ device_code: deviceCode,
80
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
81
+ }),
82
+ });
83
+ const data = await response.json();
84
+ // Check for errors
85
+ if (data.error) {
86
+ switch (data.error) {
87
+ case 'authorization_pending':
88
+ // User hasn't completed authorization yet - keep polling
89
+ return { status: 'pending' };
90
+ case 'slow_down':
91
+ // Too many requests - increase interval
92
+ session.interval += 5;
93
+ return { status: 'pending' };
94
+ case 'expired_token':
95
+ activeSessions.delete(deviceCode);
96
+ return { status: 'expired', error: 'Device code has expired' };
97
+ case 'access_denied':
98
+ activeSessions.delete(deviceCode);
99
+ return { status: 'error', error: 'User denied authorization' };
100
+ default:
101
+ return { status: 'error', error: data.error_description || data.error };
102
+ }
103
+ }
104
+ // Success! We got the token
105
+ if (data.access_token) {
106
+ activeSessions.delete(deviceCode);
107
+ return {
108
+ status: 'success',
109
+ token: {
110
+ accessToken: data.access_token,
111
+ tokenType: data.token_type,
112
+ scope: data.scope,
113
+ },
114
+ };
115
+ }
116
+ return { status: 'error', error: 'Unexpected response from GitHub' };
117
+ }
118
+ catch (error) {
119
+ const errorMsg = error instanceof Error ? error.message : String(error);
120
+ return { status: 'error', error: errorMsg };
121
+ }
122
+ }
123
+ /**
124
+ * Get the polling interval for a device code session
125
+ */
126
+ getPollingInterval(deviceCode) {
127
+ const session = activeSessions.get(deviceCode);
128
+ return session?.interval || 5;
129
+ }
130
+ /**
131
+ * Cancel an active device code session
132
+ */
133
+ cancelSession(deviceCode) {
134
+ activeSessions.delete(deviceCode);
135
+ }
136
+ /**
137
+ * Get user info using an access token
138
+ */
139
+ async getUser(accessToken) {
140
+ const response = await fetch('https://api.github.com/user', {
141
+ headers: {
142
+ 'Accept': 'application/json',
143
+ 'Authorization': `Bearer ${accessToken}`,
144
+ 'User-Agent': 'ClaudeDesk-App',
145
+ },
146
+ });
147
+ if (!response.ok) {
148
+ const error = await response.text();
149
+ throw new Error(`Failed to get user info: ${error}`);
150
+ }
151
+ const data = await response.json();
152
+ return {
153
+ login: data.login,
154
+ name: data.name,
155
+ avatarUrl: data.avatar_url,
156
+ };
157
+ }
158
+ /**
159
+ * Verify that an access token is still valid
160
+ */
161
+ async verifyToken(accessToken) {
162
+ try {
163
+ const response = await fetch('https://api.github.com/user', {
164
+ headers: {
165
+ 'Accept': 'application/json',
166
+ 'Authorization': `Bearer ${accessToken}`,
167
+ 'User-Agent': 'ClaudeDesk-App',
168
+ },
169
+ });
170
+ return response.ok;
171
+ }
172
+ catch {
173
+ return false;
174
+ }
175
+ }
176
+ }
177
+ /**
178
+ * Create a new GitHub API client with an access token
179
+ * For making authenticated GitHub API calls
180
+ */
181
+ export class GitHubAPI {
182
+ accessToken;
183
+ constructor(accessToken) {
184
+ this.accessToken = accessToken;
185
+ }
186
+ async request(endpoint, options = {}) {
187
+ const url = endpoint.startsWith('https://')
188
+ ? endpoint
189
+ : `https://api.github.com${endpoint}`;
190
+ const response = await fetch(url, {
191
+ ...options,
192
+ headers: {
193
+ 'Accept': 'application/json',
194
+ 'Authorization': `Bearer ${this.accessToken}`,
195
+ 'User-Agent': 'ClaudeDesk-App',
196
+ ...options.headers,
197
+ },
198
+ });
199
+ if (!response.ok) {
200
+ const error = await response.text();
201
+ throw new Error(`GitHub API error: ${response.status} - ${error}`);
202
+ }
203
+ return response.json();
204
+ }
205
+ async getUser() {
206
+ const data = await this.request('/user');
207
+ return {
208
+ login: data.login,
209
+ name: data.name,
210
+ avatarUrl: data.avatar_url,
211
+ };
212
+ }
213
+ async createRepo(name, isPrivate = true, description) {
214
+ const data = await this.request('/user/repos', {
215
+ method: 'POST',
216
+ headers: {
217
+ 'Content-Type': 'application/json',
218
+ },
219
+ body: JSON.stringify({
220
+ name,
221
+ private: isPrivate,
222
+ description,
223
+ auto_init: false,
224
+ }),
225
+ });
226
+ return {
227
+ name: data.name,
228
+ fullName: data.full_name,
229
+ htmlUrl: data.html_url,
230
+ cloneUrl: data.clone_url,
231
+ sshUrl: data.ssh_url,
232
+ };
233
+ }
234
+ async listRepos(page = 1, perPage = 30) {
235
+ const data = await this.request(`/user/repos?page=${page}&per_page=${perPage}&sort=updated`);
236
+ return data.map((repo) => ({
237
+ name: repo.name,
238
+ fullName: repo.full_name,
239
+ htmlUrl: repo.html_url,
240
+ private: repo.private,
241
+ }));
242
+ }
243
+ }
244
+ //# sourceMappingURL=github-oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-oauth.js","sourceRoot":"","sources":["../../src/core/github-oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ export interface MRResult {
2
+ success: boolean;
3
+ mrUrl?: string;
4
+ error?: string;
5
+ }
6
+ export declare class GitLabIntegration {
7
+ /**
8
+ * Check if GitLab CLI (glab) is installed and authenticated
9
+ */
10
+ isAvailable(): boolean;
11
+ /**
12
+ * Create a merge request using GitLab API with OAuth token.
13
+ * This method is preferred over CLI as it doesn't require `glab` to be installed.
14
+ *
15
+ * @param repoPath - Path to the repository (to get remote URL)
16
+ * @param branch - Branch to create MR from (source branch)
17
+ * @param title - MR title
18
+ * @param description - MR description
19
+ * @param token - OAuth access token
20
+ * @param targetBranch - Target branch for the MR (optional, defaults to main/master)
21
+ * @returns MRResult with success status and MR URL
22
+ */
23
+ createMRWithToken(repoPath: string, branch: string, title: string, description: string, token: string, targetBranch?: string): Promise<MRResult>;
24
+ /**
25
+ * Create a merge request.
26
+ * First tries OAuth API if workspace has a token, then falls back to glab CLI.
27
+ *
28
+ * @param repoPath - Path to the repository (working directory for git commands)
29
+ * @param branch - Branch to create MR from
30
+ * @param title - MR title
31
+ * @param body - MR body/description
32
+ * @param workspaceLookupPath - Optional path used for workspace token lookup (for worktree support)
33
+ * @returns MRResult with success status and MR URL
34
+ */
35
+ createMR(repoPath: string, branch: string, title: string, body: string, workspaceLookupPath?: string, targetBranch?: string): Promise<MRResult>;
36
+ /**
37
+ * Create a merge request using GitLab CLI (glab).
38
+ * Fallback method when OAuth is not available.
39
+ */
40
+ private createMRWithCLI;
41
+ /**
42
+ * Create a new project on GitLab and push local code
43
+ * First checks if workspace has a GitLab token, otherwise falls back to glab CLI
44
+ * @param repoPath - Path to the local repository
45
+ * @param projectName - Name for the GitLab project
46
+ * @param visibility - Project visibility (default: 'private')
47
+ * @returns Result with success status and remote URL
48
+ */
49
+ createProject(repoPath: string, projectName: string, visibility?: 'private' | 'public'): Promise<MRResult>;
50
+ /**
51
+ * Create project using GitLab API with OAuth token
52
+ */
53
+ private createProjectWithToken;
54
+ /**
55
+ * Create project using GitLab CLI (glab)
56
+ */
57
+ private createProjectWithCLI;
58
+ /**
59
+ * Get the URL to create an MR manually in the browser
60
+ * @param repoPath - Path to the repository
61
+ * @param branch - Branch to create MR from
62
+ */
63
+ getMRCreateUrl(repoPath: string, branch: string): string | null;
64
+ }
65
+ export declare const gitlabIntegration: GitLabIntegration;
66
+ //# sourceMappingURL=gitlab-integration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitlab-integration.d.ts","sourceRoot":"","sources":["../../src/core/gitlab-integration.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqBD,qBAAa,iBAAiB;IAC5B;;OAEG;IACH,WAAW,IAAI,OAAO;IAStB;;;;;;;;;;;OAWG;IACG,iBAAiB,CACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,QAAQ,CAAC;IAiFpB;;;;;;;;;;OAUG;IACG,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,mBAAmB,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAwBrJ;;;OAGG;IACH,OAAO,CAAC,eAAe;IA0CvB;;;;;;;OAOG;IACG,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,GAAE,SAAS,GAAG,QAAoB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAc3H;;OAEG;YACW,sBAAsB;IAiFpC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAiC5B;;;;OAIG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;CAoBhE;AAGD,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
@@ -0,0 +1,353 @@
1
+ import { execSync } from 'child_process';
2
+ import https from 'https';
3
+ import { workspaceManager } from '../config/workspaces.js';
4
+ /**
5
+ * Helper to make HTTPS requests using native Node.js https module.
6
+ * Avoids embedding tokens in shell commands (SEC-01 fix).
7
+ */
8
+ function httpsRequest(options, data) {
9
+ return new Promise((resolve, reject) => {
10
+ const req = https.request(options, (res) => {
11
+ let body = '';
12
+ res.on('data', (chunk) => (body += chunk));
13
+ res.on('end', () => resolve({ statusCode: res.statusCode || 0, body }));
14
+ });
15
+ req.on('error', reject);
16
+ if (data)
17
+ req.write(data);
18
+ req.end();
19
+ });
20
+ }
21
+ /**
22
+ * Parse project path from a GitLab remote URL.
23
+ * Supports both SSH and HTTPS formats.
24
+ * Returns URL-encoded project path for API calls.
25
+ */
26
+ function parseGitLabRemote(remoteUrl) {
27
+ // SSH format: git@gitlab.com:owner/repo.git or git@gitlab.com:group/subgroup/repo.git
28
+ // HTTPS format: https://gitlab.com/owner/repo.git
29
+ const match = remoteUrl.match(/gitlab\.com[:/](.+?)(?:\.git)?$/);
30
+ if (match) {
31
+ const projectPath = match[1].replace(/\.git$/, '');
32
+ return {
33
+ projectPath,
34
+ projectPathEncoded: encodeURIComponent(projectPath),
35
+ };
36
+ }
37
+ return null;
38
+ }
39
+ export class GitLabIntegration {
40
+ /**
41
+ * Check if GitLab CLI (glab) is installed and authenticated
42
+ */
43
+ isAvailable() {
44
+ try {
45
+ execSync('glab auth status', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
46
+ return true;
47
+ }
48
+ catch {
49
+ return false;
50
+ }
51
+ }
52
+ /**
53
+ * Create a merge request using GitLab API with OAuth token.
54
+ * This method is preferred over CLI as it doesn't require `glab` to be installed.
55
+ *
56
+ * @param repoPath - Path to the repository (to get remote URL)
57
+ * @param branch - Branch to create MR from (source branch)
58
+ * @param title - MR title
59
+ * @param description - MR description
60
+ * @param token - OAuth access token
61
+ * @param targetBranch - Target branch for the MR (optional, defaults to main/master)
62
+ * @returns MRResult with success status and MR URL
63
+ */
64
+ async createMRWithToken(repoPath, branch, title, description, token, targetBranch) {
65
+ try {
66
+ // Get the remote URL to determine project path
67
+ const remoteUrl = execSync('git remote get-url origin', {
68
+ cwd: repoPath,
69
+ encoding: 'utf-8',
70
+ stdio: ['pipe', 'pipe', 'pipe'],
71
+ }).trim();
72
+ const parsed = parseGitLabRemote(remoteUrl);
73
+ if (!parsed) {
74
+ return { success: false, error: 'Could not parse GitLab remote URL' };
75
+ }
76
+ const { projectPathEncoded } = parsed;
77
+ // Determine target branch if not provided
78
+ if (!targetBranch) {
79
+ try {
80
+ // Try to detect main branch
81
+ const branches = execSync('git branch -a', {
82
+ cwd: repoPath,
83
+ encoding: 'utf-8',
84
+ stdio: ['pipe', 'pipe', 'pipe'],
85
+ });
86
+ targetBranch = branches.includes('remotes/origin/main') ? 'main' : 'master';
87
+ }
88
+ catch {
89
+ targetBranch = 'main';
90
+ }
91
+ }
92
+ // Create MR via GitLab API using native https module (SEC-01 fix: no token in shell/temp files)
93
+ const requestBody = JSON.stringify({
94
+ title,
95
+ description,
96
+ source_branch: branch,
97
+ target_branch: targetBranch,
98
+ });
99
+ console.log(`[GitLabIntegration] Calling GitLab API: POST /api/v4/projects/${projectPathEncoded}/merge_requests`);
100
+ const response = await httpsRequest({
101
+ hostname: 'gitlab.com',
102
+ path: `/api/v4/projects/${projectPathEncoded}/merge_requests`,
103
+ method: 'POST',
104
+ headers: {
105
+ Accept: 'application/json',
106
+ Authorization: `Bearer ${token}`,
107
+ 'Content-Type': 'application/json',
108
+ 'Content-Length': Buffer.byteLength(requestBody),
109
+ },
110
+ }, requestBody);
111
+ console.log(`[GitLabIntegration] API response status: ${response.statusCode}`);
112
+ if (response.statusCode >= 200 && response.statusCode < 300) {
113
+ const mr = JSON.parse(response.body);
114
+ console.log(`[GitLabIntegration] MR created via API: ${mr.web_url}`);
115
+ return { success: true, mrUrl: mr.web_url };
116
+ }
117
+ else {
118
+ let errorMsg = response.body;
119
+ try {
120
+ const err = JSON.parse(response.body);
121
+ errorMsg = err.message || err.error || JSON.stringify(err);
122
+ }
123
+ catch {
124
+ errorMsg = `HTTP ${response.statusCode}: ${response.body.substring(0, 200)}`;
125
+ }
126
+ console.error(`[GitLabIntegration] API error creating MR: ${errorMsg}`);
127
+ return { success: false, error: errorMsg };
128
+ }
129
+ }
130
+ catch (error) {
131
+ const err = error;
132
+ const errorMsg = err.stderr?.toString() || err.message || 'Unknown error';
133
+ console.error(`[GitLabIntegration] Failed to create MR via API: ${errorMsg}`);
134
+ return { success: false, error: errorMsg };
135
+ }
136
+ }
137
+ /**
138
+ * Create a merge request.
139
+ * First tries OAuth API if workspace has a token, then falls back to glab CLI.
140
+ *
141
+ * @param repoPath - Path to the repository (working directory for git commands)
142
+ * @param branch - Branch to create MR from
143
+ * @param title - MR title
144
+ * @param body - MR body/description
145
+ * @param workspaceLookupPath - Optional path used for workspace token lookup (for worktree support)
146
+ * @returns MRResult with success status and MR URL
147
+ */
148
+ async createMR(repoPath, branch, title, body, workspaceLookupPath, targetBranch) {
149
+ // First, check if this repo belongs to a workspace with GitLab token
150
+ // Use workspaceLookupPath if provided (for worktree mode), otherwise use repoPath
151
+ const lookupPath = workspaceLookupPath || repoPath;
152
+ console.log(`[GitLabIntegration] Looking up workspace for path: ${lookupPath}`);
153
+ const workspace = workspaceManager.getWorkspaceForRepo(lookupPath);
154
+ console.log(`[GitLabIntegration] Found workspace: ${workspace ? workspace.name + ' (id: ' + workspace.id + ', scanPath: ' + workspace.scanPath + ')' : 'null'}`);
155
+ const tokenData = workspace ? workspaceManager.getGitLabToken(workspace.id) : null;
156
+ console.log(`[GitLabIntegration] Token data exists: ${!!tokenData}`);
157
+ if (tokenData) {
158
+ console.log(`[GitLabIntegration] Using OAuth token for MR creation`);
159
+ const result = await this.createMRWithToken(repoPath, branch, title, body, tokenData.accessToken, targetBranch);
160
+ if (result.success) {
161
+ return result;
162
+ }
163
+ // If API fails, fall through to CLI
164
+ console.log(`[GitLabIntegration] OAuth MR creation failed, trying CLI fallback: ${result.error}`);
165
+ }
166
+ // Fall back to glab CLI
167
+ return this.createMRWithCLI(repoPath, branch, title, body, targetBranch);
168
+ }
169
+ /**
170
+ * Create a merge request using GitLab CLI (glab).
171
+ * Fallback method when OAuth is not available.
172
+ */
173
+ createMRWithCLI(repoPath, branch, title, body, targetBranch) {
174
+ if (!this.isAvailable()) {
175
+ return {
176
+ success: false,
177
+ error: 'GitLab CLI (glab) is not installed or not authenticated. Install from https://gitlab.com/gitlab-org/cli, or connect GitLab OAuth in workspace settings.',
178
+ };
179
+ }
180
+ try {
181
+ // Escape special characters in title and body for shell
182
+ const escapedTitle = title.replace(/"/g, '\\"').replace(/`/g, '\\`');
183
+ const escapedBody = body.replace(/"/g, '\\"').replace(/`/g, '\\`');
184
+ // Build command with optional target branch
185
+ let cmd = `glab mr create --title "${escapedTitle}" --description "${escapedBody}" --source-branch "${branch}"`;
186
+ if (targetBranch) {
187
+ cmd += ` --target-branch "${targetBranch}"`;
188
+ }
189
+ const result = execSync(cmd, {
190
+ cwd: repoPath,
191
+ encoding: 'utf-8',
192
+ stdio: ['pipe', 'pipe', 'pipe'],
193
+ timeout: 60000, // 60s timeout
194
+ }).trim();
195
+ // glab mr create outputs the MR URL on success
196
+ const mrUrl = result.match(/https:\/\/gitlab\.com\/.+\/merge_requests\/\d+/)?.[0] || result;
197
+ console.log(`[GitLabIntegration] MR created via CLI: ${mrUrl}`);
198
+ return { success: true, mrUrl };
199
+ }
200
+ catch (error) {
201
+ const err = error;
202
+ const errorMsg = err.stderr?.toString() || err.message || 'Unknown error';
203
+ console.error(`[GitLabIntegration] Failed to create MR via CLI: ${errorMsg}`);
204
+ return { success: false, error: errorMsg };
205
+ }
206
+ }
207
+ /**
208
+ * Create a new project on GitLab and push local code
209
+ * First checks if workspace has a GitLab token, otherwise falls back to glab CLI
210
+ * @param repoPath - Path to the local repository
211
+ * @param projectName - Name for the GitLab project
212
+ * @param visibility - Project visibility (default: 'private')
213
+ * @returns Result with success status and remote URL
214
+ */
215
+ async createProject(repoPath, projectName, visibility = 'private') {
216
+ // First, check if this repo belongs to a workspace with GitLab token
217
+ const workspace = workspaceManager.getWorkspaceForRepo(repoPath);
218
+ const tokenData = workspace ? workspaceManager.getGitLabToken(workspace.id) : null;
219
+ if (tokenData) {
220
+ // Use GitLab API with workspace token
221
+ return this.createProjectWithToken(repoPath, projectName, visibility, tokenData.accessToken);
222
+ }
223
+ // Fall back to glab CLI
224
+ return this.createProjectWithCLI(repoPath, projectName, visibility);
225
+ }
226
+ /**
227
+ * Create project using GitLab API with OAuth token
228
+ */
229
+ async createProjectWithToken(repoPath, projectName, visibility, token) {
230
+ try {
231
+ // Create project via GitLab API using native https module (SEC-01 fix: no token in shell)
232
+ const projectData = JSON.stringify({
233
+ name: projectName,
234
+ visibility,
235
+ initialize_with_readme: false,
236
+ });
237
+ const response = await httpsRequest({
238
+ hostname: 'gitlab.com',
239
+ path: '/api/v4/projects',
240
+ method: 'POST',
241
+ headers: {
242
+ Accept: 'application/json',
243
+ Authorization: `Bearer ${token}`,
244
+ 'Content-Type': 'application/json',
245
+ 'Content-Length': Buffer.byteLength(projectData),
246
+ },
247
+ }, projectData);
248
+ if (response.statusCode < 200 || response.statusCode >= 300) {
249
+ console.error(`[GitLabIntegration] API error: ${response.body}`);
250
+ return { success: false, error: response.body };
251
+ }
252
+ const project = JSON.parse(response.body);
253
+ const webUrl = project.web_url;
254
+ const httpUrlToRepo = project.http_url_to_repo;
255
+ // Now add remote and push using git commands with credential helper
256
+ const { createCredentialHelperScript, removeCredentialHelperScript } = await import('./git-credential-helper.js');
257
+ const scriptPath = createCredentialHelperScript(token, 'gitlab');
258
+ try {
259
+ // Add remote (without token in URL)
260
+ execSync(`git remote add origin "${httpUrlToRepo}"`, {
261
+ cwd: repoPath,
262
+ encoding: 'utf-8',
263
+ stdio: ['pipe', 'pipe', 'pipe'],
264
+ });
265
+ // Push with credential helper
266
+ const pushCmd = process.platform === 'win32'
267
+ ? 'cmd /c "git push -u origin main || git push -u origin master"'
268
+ : 'git push -u origin main || git push -u origin master';
269
+ execSync(pushCmd, {
270
+ cwd: repoPath,
271
+ encoding: 'utf-8',
272
+ stdio: ['pipe', 'pipe', 'pipe'],
273
+ timeout: 120000,
274
+ env: {
275
+ ...process.env,
276
+ GIT_ASKPASS: scriptPath,
277
+ GIT_TERMINAL_PROMPT: '0',
278
+ },
279
+ });
280
+ }
281
+ finally {
282
+ // Always clean up credential helper script
283
+ removeCredentialHelperScript(scriptPath);
284
+ }
285
+ console.log(`[GitLabIntegration] Project created via API: ${webUrl}`);
286
+ return { success: true, mrUrl: webUrl };
287
+ }
288
+ catch (error) {
289
+ const err = error;
290
+ const errorMsg = err.stderr?.toString() || err.message || 'Unknown error';
291
+ console.error(`[GitLabIntegration] Failed to create project via API: ${errorMsg}`);
292
+ return { success: false, error: errorMsg };
293
+ }
294
+ }
295
+ /**
296
+ * Create project using GitLab CLI (glab)
297
+ */
298
+ createProjectWithCLI(repoPath, projectName, visibility) {
299
+ if (!this.isAvailable()) {
300
+ return {
301
+ success: false,
302
+ error: 'GitLab CLI (glab) is not installed or not authenticated. Install from https://gitlab.com/gitlab-org/cli',
303
+ };
304
+ }
305
+ try {
306
+ // glab repo create will create the project and set up remote origin
307
+ const result = execSync(`glab repo create ${projectName} --${visibility} --source=. --remote=origin --push`, {
308
+ cwd: repoPath,
309
+ encoding: 'utf-8',
310
+ stdio: ['pipe', 'pipe', 'pipe'],
311
+ timeout: 120000, // 2 min timeout for push
312
+ }).trim();
313
+ // Extract the project URL from output
314
+ const projectUrl = result.match(/https:\/\/gitlab\.com\/[^\s]+/)?.[0] || result;
315
+ console.log(`[GitLabIntegration] Project created via CLI: ${projectUrl}`);
316
+ return { success: true, mrUrl: projectUrl };
317
+ }
318
+ catch (error) {
319
+ const err = error;
320
+ const errorMsg = err.stderr?.toString() || err.message || 'Unknown error';
321
+ console.error(`[GitLabIntegration] Failed to create project via CLI: ${errorMsg}`);
322
+ return { success: false, error: errorMsg };
323
+ }
324
+ }
325
+ /**
326
+ * Get the URL to create an MR manually in the browser
327
+ * @param repoPath - Path to the repository
328
+ * @param branch - Branch to create MR from
329
+ */
330
+ getMRCreateUrl(repoPath, branch) {
331
+ try {
332
+ // Get the remote URL
333
+ const remoteUrl = execSync('git remote get-url origin', {
334
+ cwd: repoPath,
335
+ encoding: 'utf-8',
336
+ stdio: ['pipe', 'pipe', 'pipe'],
337
+ }).trim();
338
+ // Parse GitLab URL from remote
339
+ // Supports: git@gitlab.com:user/repo.git, https://gitlab.com/user/repo.git
340
+ let match = remoteUrl.match(/gitlab\.com[:/]([^/]+\/[^/.]+)/);
341
+ if (!match)
342
+ return null;
343
+ const pathWithNamespace = match[1].replace(/\.git$/, '');
344
+ return `https://gitlab.com/${pathWithNamespace}/-/merge_requests/new?merge_request%5Bsource_branch%5D=${branch}`;
345
+ }
346
+ catch {
347
+ return null;
348
+ }
349
+ }
350
+ }
351
+ // Singleton instance
352
+ export const gitlabIntegration = new GitLabIntegration();
353
+ //# sourceMappingURL=gitlab-integration.js.map