vibe-weaver 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.
@@ -0,0 +1,498 @@
1
+ /**
2
+ * Git tools for Vibe-Weaver CLI
3
+ * Handles local git operations and GitHub integration
4
+ */
5
+
6
+ import simpleGit, { SimpleGit, LogResult, StatusResult } from 'simple-git';
7
+ import chalk from 'chalk';
8
+ import { getGithubToken } from '../auth/index.js';
9
+ import { getSupabaseUrl } from '../config/index.js';
10
+
11
+ export interface GitStatus {
12
+ current: string | null;
13
+ tracking: string | null;
14
+ ahead: number;
15
+ behind: number;
16
+ staged: string[];
17
+ modified: string[];
18
+ untracked: string[];
19
+ conflicted: string[];
20
+ }
21
+
22
+ export interface GitCommitResult {
23
+ success: boolean;
24
+ commitSha?: string;
25
+ message?: string;
26
+ files?: string[];
27
+ error?: string;
28
+ }
29
+
30
+ export interface GitLogEntry {
31
+ hash: string;
32
+ date: string;
33
+ message: string;
34
+ author_name: string;
35
+ author_email: string;
36
+ }
37
+
38
+ /**
39
+ * Get a git instance for a directory
40
+ */
41
+ export function getGit(cwd: string = process.cwd()): SimpleGit {
42
+ return simpleGit(cwd);
43
+ }
44
+
45
+ /**
46
+ * Get the current git status
47
+ */
48
+ export async function getStatus(cwd: string = process.cwd()): Promise<GitStatus> {
49
+ const git = getGit(cwd);
50
+ const status = await git.status();
51
+
52
+ return {
53
+ current: status.current,
54
+ tracking: status.tracking,
55
+ ahead: status.ahead,
56
+ behind: status.behind,
57
+ staged: status.staged,
58
+ modified: status.modified,
59
+ untracked: status.not_added,
60
+ conflicted: status.conflicted
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Get the current branch name
66
+ */
67
+ export async function getBranch(cwd: string = process.cwd()): Promise<string> {
68
+ const git = getGit(cwd);
69
+ const branchSummary = await git.branch();
70
+ return branchSummary.current;
71
+ }
72
+
73
+ /**
74
+ * Get recent commit history
75
+ */
76
+ export async function getLog(cwd: string = process.cwd(), maxCount: number = 10): Promise<GitLogEntry[]> {
77
+ const git = getGit(cwd);
78
+ const log = await git.log({ maxCount });
79
+
80
+ return log.all.map(entry => ({
81
+ hash: entry.hash,
82
+ date: entry.date,
83
+ message: entry.message,
84
+ author_name: entry.author_name,
85
+ author_email: entry.author_email
86
+ }));
87
+ }
88
+
89
+ /**
90
+ * Stage files for commit
91
+ */
92
+ export async function stage(files: string | string[], cwd: string = process.cwd()): Promise<void> {
93
+ const git = getGit(cwd);
94
+ await git.add(files);
95
+ }
96
+
97
+ /**
98
+ * Stage all changes
99
+ */
100
+ export async function stageAll(cwd: string = process.cwd()): Promise<void> {
101
+ const git = getGit(cwd);
102
+ await git.add('.');
103
+ }
104
+
105
+ /**
106
+ * Commit changes with a message
107
+ */
108
+ export async function commit(message: string, cwd: string = process.cwd()): Promise<GitCommitResult> {
109
+ const git = getGit(cwd);
110
+
111
+ try {
112
+ const result = await git.commit(message);
113
+
114
+ return {
115
+ success: true,
116
+ commitSha: result.commit,
117
+ message: result.summary.changes + ' file(s) changed'
118
+ };
119
+ } catch (error) {
120
+ return {
121
+ success: false,
122
+ error: error instanceof Error ? error.message : String(error)
123
+ };
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Commit all changes with a conventional commit message
129
+ */
130
+ export async function commitAll(message: string, cwd: string = process.cwd()): Promise<GitCommitResult> {
131
+ await stageAll(cwd);
132
+ return commit(message, cwd);
133
+ }
134
+
135
+ /**
136
+ * Push changes to remote
137
+ */
138
+ export async function push(options: { remote?: string; branch?: string; force?: boolean } = {}, cwd: string = process.cwd()): Promise<GitCommitResult> {
139
+ const git = getGit(cwd);
140
+
141
+ try {
142
+ const result = await git.push(options.remote || 'origin', options.branch || 'HEAD', {
143
+ '--force': options.force || false
144
+ });
145
+
146
+ return {
147
+ success: true,
148
+ commitSha: result.pushed[0]?.hash,
149
+ message: 'Push successful'
150
+ };
151
+ } catch (error) {
152
+ return {
153
+ success: false,
154
+ error: error instanceof Error ? error.message : String(error)
155
+ };
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Pull changes from remote
161
+ */
162
+ export async function pull(options: { remote?: string; branch?: string } = {}, cwd: string = process.cwd()): Promise<GitCommitResult> {
163
+ const git = getGit(cwd);
164
+
165
+ try {
166
+ const result = await git.pull(options.remote || 'origin', options.branch || 'HEAD');
167
+
168
+ return {
169
+ success: true,
170
+ message: 'Pull successful'
171
+ };
172
+ } catch (error) {
173
+ return {
174
+ success: false,
175
+ error: error instanceof Error ? error.message : String(error)
176
+ };
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Create a new branch
182
+ */
183
+ export async function createBranch(branchName: string, options: { checkout?: boolean } = {}, cwd: string = process.cwd()): Promise<GitCommitResult> {
184
+ const git = getGit(cwd);
185
+
186
+ try {
187
+ if (options.checkout) {
188
+ await git.checkoutLocalBranch(branchName);
189
+ } else {
190
+ await git.branch([branchName]);
191
+ }
192
+
193
+ return {
194
+ success: true,
195
+ message: `Branch ${branchName} created`
196
+ };
197
+ } catch (error) {
198
+ return {
199
+ success: false,
200
+ error: error instanceof Error ? error.message : String(error)
201
+ };
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Checkout a branch
207
+ */
208
+ export async function checkout(branchName: string, cwd: string = process.cwd()): Promise<GitCommitResult> {
209
+ const git = getGit(cwd);
210
+
211
+ try {
212
+ await git.checkout(branchName);
213
+
214
+ return {
215
+ success: true,
216
+ message: `Switched to branch ${branchName}`
217
+ };
218
+ } catch (error) {
219
+ return {
220
+ success: false,
221
+ error: error instanceof Error ? error.message : String(error)
222
+ };
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Get the diff between commits, branches, or working tree
228
+ */
229
+ export async function getDiff(options: { staged?: boolean; from?: string; to?: string } = {}, cwd: string = process.cwd()): Promise<string> {
230
+ const git = getGit(cwd);
231
+
232
+ if (options.staged) {
233
+ return git.diff(['--staged']);
234
+ }
235
+
236
+ if (options.from && options.to) {
237
+ return git.diff([options.from, options.to]);
238
+ }
239
+
240
+ return git.diff();
241
+ }
242
+
243
+ /**
244
+ * Check if a directory is a git repository
245
+ */
246
+ export async function isRepo(cwd: string = process.cwd()): Promise<boolean> {
247
+ const git = getGit(cwd);
248
+ return git.checkIsRepo();
249
+ }
250
+
251
+ /**
252
+ * Initialize a new git repository
253
+ */
254
+ export async function init(cwd: string = process.cwd()): Promise<GitCommitResult> {
255
+ const git = getGit(cwd);
256
+
257
+ try {
258
+ await git.init();
259
+
260
+ return {
261
+ success: true,
262
+ message: 'Git repository initialized'
263
+ };
264
+ } catch (error) {
265
+ return {
266
+ success: false,
267
+ error: error instanceof Error ? error.message : String(error)
268
+ };
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Get the remote URL
274
+ */
275
+ export async function getRemote(cwd: string = process.cwd()): Promise<string | null> {
276
+ const git = getGit(cwd);
277
+ const remotes = await git.getRemotes(true);
278
+
279
+ const origin = remotes.find(r => r.name === 'origin');
280
+ return origin?.refs.fetch || null;
281
+ }
282
+
283
+ // GitHub-specific operations via the platform's edge function
284
+
285
+ /**
286
+ * Call the github-ai-sync edge function
287
+ */
288
+ async function callGithubSync(action: string, params: Record<string, any>): Promise<any> {
289
+ const apiKey = await getGithubToken();
290
+ if (!apiKey) {
291
+ throw new Error('GitHub token not configured. Run "vibe-weaver login --github" first.');
292
+ }
293
+
294
+ const supabaseUrl = getSupabaseUrl();
295
+ const endpoint = `${supabaseUrl}/functions/v1/github-ai-sync`;
296
+
297
+ const response = await fetch(endpoint, {
298
+ method: 'POST',
299
+ headers: {
300
+ 'Content-Type': 'application/json',
301
+ 'Authorization': `Bearer ${apiKey}`
302
+ },
303
+ body: JSON.stringify({
304
+ action,
305
+ token: apiKey,
306
+ ...params
307
+ })
308
+ });
309
+
310
+ if (!response.ok) {
311
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }));
312
+ throw new Error(`GitHub API error: ${error.error || error.message}`);
313
+ }
314
+
315
+ return response.json();
316
+ }
317
+
318
+ /**
319
+ * Commit files via GitHub API
320
+ */
321
+ export async function commitFilesToGithub(options: {
322
+ owner: string;
323
+ repo: string;
324
+ branch: string;
325
+ files: { path: string; content: string; message: string }[];
326
+ commitMessage: string;
327
+ }): Promise<GitCommitResult> {
328
+ try {
329
+ const result = await callGithubSync('commit_files', options);
330
+
331
+ return {
332
+ success: true,
333
+ commitSha: result.commitSha,
334
+ message: `Committed ${result.files?.length || 0} file(s) to ${options.branch}`,
335
+ files: result.files
336
+ };
337
+ } catch (error) {
338
+ return {
339
+ success: false,
340
+ error: error instanceof Error ? error.message : String(error)
341
+ };
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Create a branch via GitHub API
347
+ */
348
+ export async function createBranchOnGithub(options: {
349
+ owner: string;
350
+ repo: string;
351
+ newBranch: string;
352
+ fromBranch: string;
353
+ }): Promise<GitCommitResult> {
354
+ try {
355
+ const result = await callGithubSync('create_branch', options);
356
+
357
+ return {
358
+ success: true,
359
+ commitSha: result.sha,
360
+ message: `Created branch ${options.newBranch} from ${options.fromBranch}`
361
+ };
362
+ } catch (error) {
363
+ return {
364
+ success: false,
365
+ error: error instanceof Error ? error.message : String(error)
366
+ };
367
+ }
368
+ }
369
+
370
+ /**
371
+ * Create a pull request via GitHub API
372
+ */
373
+ export async function createPullRequestOnGithub(options: {
374
+ owner: string;
375
+ repo: string;
376
+ title: string;
377
+ body: string;
378
+ head: string;
379
+ base: string;
380
+ }): Promise<{ success: boolean; prNumber?: number; prUrl?: string; error?: string }> {
381
+ try {
382
+ const result = await callGithubSync('create_pr', options);
383
+
384
+ return {
385
+ success: true,
386
+ prNumber: result.prNumber,
387
+ prUrl: result.prUrl
388
+ };
389
+ } catch (error) {
390
+ return {
391
+ success: false,
392
+ error: error instanceof Error ? error.message : String(error)
393
+ };
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Full PR workflow: create branch, commit changes, open PR
399
+ */
400
+ export async function fullPRWorkflow(options: {
401
+ owner: string;
402
+ repo: string;
403
+ branchName: string;
404
+ baseBranch: string;
405
+ files: { path: string; content: string; message: string }[];
406
+ commitMessage: string;
407
+ prTitle: string;
408
+ prBody: string;
409
+ }): Promise<{
410
+ success: boolean;
411
+ branch?: string;
412
+ commitSha?: string;
413
+ prUrl?: string;
414
+ prNumber?: number;
415
+ error?: string;
416
+ }> {
417
+ console.log(chalk.blue('\n=== GitHub PR Workflow ==='));
418
+
419
+ // Step 1: Create branch
420
+ console.log(chalk.gray('1. Creating branch...'));
421
+ const branchResult = await createBranchOnGithub({
422
+ owner: options.owner,
423
+ repo: options.repo,
424
+ newBranch: options.branchName,
425
+ fromBranch: options.baseBranch
426
+ });
427
+
428
+ if (!branchResult.success) {
429
+ return { success: false, error: `Failed to create branch: ${branchResult.error}` };
430
+ }
431
+
432
+ console.log(chalk.green(` ✓ Branch ${options.branchName} created`));
433
+
434
+ // Step 2: Commit files
435
+ console.log(chalk.gray('2. Committing files...'));
436
+ const commitResult = await commitFilesToGithub({
437
+ owner: options.owner,
438
+ repo: options.repo,
439
+ branch: options.branchName,
440
+ files: options.files,
441
+ commitMessage: options.commitMessage
442
+ });
443
+
444
+ if (!commitResult.success) {
445
+ return { success: false, error: `Failed to commit: ${commitResult.error}` };
446
+ }
447
+
448
+ console.log(chalk.green(` ✓ Committed ${commitResult.files?.length || 0} file(s)`));
449
+
450
+ // Step 3: Create PR
451
+ console.log(chalk.gray('3. Creating pull request...'));
452
+ const prResult = await createPullRequestOnGithub({
453
+ owner: options.owner,
454
+ repo: options.repo,
455
+ title: options.prTitle,
456
+ body: options.prBody,
457
+ head: options.branchName,
458
+ base: options.baseBranch
459
+ });
460
+
461
+ if (!prResult.success) {
462
+ return { success: false, error: `Failed to create PR: ${prResult.error}` };
463
+ }
464
+
465
+ console.log(chalk.green(` ✓ PR created: ${prResult.prUrl}`));
466
+ console.log(chalk.blue('===========================\n'));
467
+
468
+ return {
469
+ success: true,
470
+ branch: options.branchName,
471
+ commitSha: commitResult.commitSha,
472
+ prUrl: prResult.prUrl,
473
+ prNumber: prResult.prNumber
474
+ };
475
+ }
476
+
477
+ export default {
478
+ getGit,
479
+ getStatus,
480
+ getBranch,
481
+ getLog,
482
+ stage,
483
+ stageAll,
484
+ commit,
485
+ commitAll,
486
+ push,
487
+ pull,
488
+ createBranch,
489
+ checkout,
490
+ getDiff,
491
+ isRepo,
492
+ init,
493
+ getRemote,
494
+ commitFilesToGithub,
495
+ createBranchOnGithub,
496
+ createPullRequestOnGithub,
497
+ fullPRWorkflow
498
+ };