@agi-cli/server 0.1.81 → 0.1.83
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/package.json +3 -3
- package/src/index.ts +2 -2
- package/src/openapi/paths/git.ts +104 -0
- package/src/routes/config/agents.ts +44 -0
- package/src/routes/config/cwd.ts +21 -0
- package/src/routes/config/index.ts +14 -0
- package/src/routes/config/main.ts +68 -0
- package/src/routes/config/models.ts +109 -0
- package/src/routes/config/providers.ts +46 -0
- package/src/routes/config/utils.ts +107 -0
- package/src/routes/files.ts +60 -2
- package/src/routes/git/branch.ts +75 -0
- package/src/routes/git/commit.ts +159 -0
- package/src/routes/git/diff.ts +137 -0
- package/src/routes/git/index.ts +18 -0
- package/src/routes/git/push.ts +160 -0
- package/src/routes/git/schemas.ts +47 -0
- package/src/routes/git/staging.ts +208 -0
- package/src/routes/git/status.ts +76 -0
- package/src/routes/git/types.ts +19 -0
- package/src/routes/git/utils.ts +212 -0
- package/src/runtime/runner.ts +3 -6
- package/src/runtime/session-manager.ts +1 -1
- package/src/routes/config.ts +0 -387
- package/src/routes/git.ts +0 -980
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Hono } from 'hono';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { gitStatusSchema } from './schemas.ts';
|
|
5
|
+
import {
|
|
6
|
+
validateAndGetGitRoot,
|
|
7
|
+
getAheadBehind,
|
|
8
|
+
getCurrentBranch,
|
|
9
|
+
} from './utils.ts';
|
|
10
|
+
|
|
11
|
+
const execFileAsync = promisify(execFile);
|
|
12
|
+
|
|
13
|
+
export function registerBranchRoute(app: Hono) {
|
|
14
|
+
app.get('/v1/git/branch', async (c) => {
|
|
15
|
+
try {
|
|
16
|
+
const query = gitStatusSchema.parse({
|
|
17
|
+
project: c.req.query('project'),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const requestedPath = query.project || process.cwd();
|
|
21
|
+
|
|
22
|
+
const validation = await validateAndGetGitRoot(requestedPath);
|
|
23
|
+
if ('error' in validation) {
|
|
24
|
+
return c.json(
|
|
25
|
+
{ status: 'error', error: validation.error, code: validation.code },
|
|
26
|
+
400,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { gitRoot } = validation;
|
|
31
|
+
|
|
32
|
+
const branch = await getCurrentBranch(gitRoot);
|
|
33
|
+
|
|
34
|
+
const { ahead, behind } = await getAheadBehind(gitRoot);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const { stdout: remotes } = await execFileAsync('git', ['remote'], {
|
|
38
|
+
cwd: gitRoot,
|
|
39
|
+
});
|
|
40
|
+
const remoteList = remotes.trim().split('\n').filter(Boolean);
|
|
41
|
+
|
|
42
|
+
return c.json({
|
|
43
|
+
status: 'ok',
|
|
44
|
+
data: {
|
|
45
|
+
branch,
|
|
46
|
+
ahead,
|
|
47
|
+
behind,
|
|
48
|
+
remotes: remoteList,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
} catch {
|
|
52
|
+
return c.json({
|
|
53
|
+
status: 'ok',
|
|
54
|
+
data: {
|
|
55
|
+
branch,
|
|
56
|
+
ahead,
|
|
57
|
+
behind,
|
|
58
|
+
remotes: [],
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
return c.json(
|
|
64
|
+
{
|
|
65
|
+
status: 'error',
|
|
66
|
+
error:
|
|
67
|
+
error instanceof Error
|
|
68
|
+
? error.message
|
|
69
|
+
: 'Failed to get branch info',
|
|
70
|
+
},
|
|
71
|
+
500,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { Hono } from 'hono';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { generateText } from 'ai';
|
|
5
|
+
import type { ProviderId } from '@agi-cli/sdk';
|
|
6
|
+
import { loadConfig, getAuth } from '@agi-cli/sdk';
|
|
7
|
+
import { gitCommitSchema, gitGenerateCommitMessageSchema } from './schemas.ts';
|
|
8
|
+
import { validateAndGetGitRoot, parseGitStatus } from './utils.ts';
|
|
9
|
+
import { resolveModel } from '../../runtime/provider.ts';
|
|
10
|
+
import { getProviderSpoofPrompt } from '../../runtime/prompt.ts';
|
|
11
|
+
|
|
12
|
+
const execFileAsync = promisify(execFile);
|
|
13
|
+
|
|
14
|
+
export function registerCommitRoutes(app: Hono) {
|
|
15
|
+
app.post('/v1/git/commit', async (c) => {
|
|
16
|
+
try {
|
|
17
|
+
const body = await c.req.json();
|
|
18
|
+
const { message, project } = gitCommitSchema.parse(body);
|
|
19
|
+
|
|
20
|
+
const requestedPath = project || process.cwd();
|
|
21
|
+
|
|
22
|
+
const validation = await validateAndGetGitRoot(requestedPath);
|
|
23
|
+
if ('error' in validation) {
|
|
24
|
+
return c.json(
|
|
25
|
+
{ status: 'error', error: validation.error, code: validation.code },
|
|
26
|
+
400,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { gitRoot } = validation;
|
|
31
|
+
|
|
32
|
+
const { stdout } = await execFileAsync('git', ['commit', '-m', message], {
|
|
33
|
+
cwd: gitRoot,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return c.json({
|
|
37
|
+
status: 'ok',
|
|
38
|
+
data: {
|
|
39
|
+
message: stdout.trim(),
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return c.json(
|
|
44
|
+
{
|
|
45
|
+
status: 'error',
|
|
46
|
+
error: error instanceof Error ? error.message : 'Failed to commit',
|
|
47
|
+
},
|
|
48
|
+
500,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
app.post('/v1/git/generate-commit-message', async (c) => {
|
|
54
|
+
try {
|
|
55
|
+
const body = await c.req.json();
|
|
56
|
+
const { project } = gitGenerateCommitMessageSchema.parse(body);
|
|
57
|
+
|
|
58
|
+
const requestedPath = project || process.cwd();
|
|
59
|
+
|
|
60
|
+
const validation = await validateAndGetGitRoot(requestedPath);
|
|
61
|
+
if ('error' in validation) {
|
|
62
|
+
return c.json(
|
|
63
|
+
{ status: 'error', error: validation.error, code: validation.code },
|
|
64
|
+
400,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { gitRoot } = validation;
|
|
69
|
+
|
|
70
|
+
const { stdout: diff } = await execFileAsync(
|
|
71
|
+
'git',
|
|
72
|
+
['diff', '--cached'],
|
|
73
|
+
{
|
|
74
|
+
cwd: gitRoot,
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (!diff.trim()) {
|
|
79
|
+
return c.json(
|
|
80
|
+
{
|
|
81
|
+
status: 'error',
|
|
82
|
+
error: 'No staged changes to generate message from',
|
|
83
|
+
},
|
|
84
|
+
400,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const { stdout: statusOutput } = await execFileAsync(
|
|
89
|
+
'git',
|
|
90
|
+
['status', '--porcelain=v2'],
|
|
91
|
+
{ cwd: gitRoot },
|
|
92
|
+
);
|
|
93
|
+
const { staged } = parseGitStatus(statusOutput, gitRoot);
|
|
94
|
+
const fileList = staged.map((f) => `${f.status}: ${f.path}`).join('\n');
|
|
95
|
+
|
|
96
|
+
const config = await loadConfig();
|
|
97
|
+
|
|
98
|
+
const provider = (config.defaults?.provider || 'anthropic') as ProviderId;
|
|
99
|
+
const modelId = config.defaults?.model || 'claude-3-5-sonnet-20241022';
|
|
100
|
+
|
|
101
|
+
const auth = await getAuth(provider, config.projectRoot);
|
|
102
|
+
const needsSpoof = auth?.type === 'oauth';
|
|
103
|
+
const spoofPrompt = needsSpoof
|
|
104
|
+
? getProviderSpoofPrompt(provider)
|
|
105
|
+
: undefined;
|
|
106
|
+
|
|
107
|
+
const model = await resolveModel(provider, modelId, config);
|
|
108
|
+
|
|
109
|
+
const userPrompt = `Generate a concise, conventional commit message for these git changes.
|
|
110
|
+
|
|
111
|
+
Staged files:
|
|
112
|
+
${fileList}
|
|
113
|
+
|
|
114
|
+
Diff (first 2000 chars):
|
|
115
|
+
${diff.slice(0, 2000)}
|
|
116
|
+
|
|
117
|
+
Guidelines:
|
|
118
|
+
- Use conventional commits format (feat:, fix:, docs:, etc.)
|
|
119
|
+
- Keep the first line under 72 characters
|
|
120
|
+
- Be specific but concise
|
|
121
|
+
- Focus on what changed and why, not how
|
|
122
|
+
- Do not include any markdown formatting or code blocks
|
|
123
|
+
- Return ONLY the commit message text, nothing else
|
|
124
|
+
|
|
125
|
+
Commit message:`;
|
|
126
|
+
|
|
127
|
+
const systemPrompt = spoofPrompt
|
|
128
|
+
? spoofPrompt
|
|
129
|
+
: 'You are a helpful assistant that generates git commit messages.';
|
|
130
|
+
|
|
131
|
+
const { text } = await generateText({
|
|
132
|
+
model,
|
|
133
|
+
system: systemPrompt,
|
|
134
|
+
prompt: userPrompt,
|
|
135
|
+
maxTokens: 200,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const message = text.trim();
|
|
139
|
+
|
|
140
|
+
return c.json({
|
|
141
|
+
status: 'ok',
|
|
142
|
+
data: {
|
|
143
|
+
message,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
} catch (error) {
|
|
147
|
+
return c.json(
|
|
148
|
+
{
|
|
149
|
+
status: 'error',
|
|
150
|
+
error:
|
|
151
|
+
error instanceof Error
|
|
152
|
+
? error.message
|
|
153
|
+
: 'Failed to generate commit message',
|
|
154
|
+
},
|
|
155
|
+
500,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { Hono } from 'hono';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { readFile } from 'node:fs/promises';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { gitDiffSchema } from './schemas.ts';
|
|
7
|
+
import {
|
|
8
|
+
validateAndGetGitRoot,
|
|
9
|
+
checkIfNewFile,
|
|
10
|
+
inferLanguage,
|
|
11
|
+
summarizeDiff,
|
|
12
|
+
} from './utils.ts';
|
|
13
|
+
|
|
14
|
+
const execFileAsync = promisify(execFile);
|
|
15
|
+
|
|
16
|
+
export function registerDiffRoute(app: Hono) {
|
|
17
|
+
app.get('/v1/git/diff', async (c) => {
|
|
18
|
+
try {
|
|
19
|
+
const query = gitDiffSchema.parse({
|
|
20
|
+
project: c.req.query('project'),
|
|
21
|
+
file: c.req.query('file'),
|
|
22
|
+
staged: c.req.query('staged'),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const requestedPath = query.project || process.cwd();
|
|
26
|
+
|
|
27
|
+
const validation = await validateAndGetGitRoot(requestedPath);
|
|
28
|
+
if ('error' in validation) {
|
|
29
|
+
return c.json(
|
|
30
|
+
{ status: 'error', error: validation.error, code: validation.code },
|
|
31
|
+
400,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { gitRoot } = validation;
|
|
36
|
+
const absPath = join(gitRoot, query.file);
|
|
37
|
+
|
|
38
|
+
const isNewFile = await checkIfNewFile(gitRoot, query.file);
|
|
39
|
+
|
|
40
|
+
if (isNewFile) {
|
|
41
|
+
try {
|
|
42
|
+
const content = await readFile(absPath, 'utf-8');
|
|
43
|
+
const lineCount = content.split('\n').length;
|
|
44
|
+
const language = inferLanguage(query.file);
|
|
45
|
+
|
|
46
|
+
return c.json({
|
|
47
|
+
status: 'ok',
|
|
48
|
+
data: {
|
|
49
|
+
file: query.file,
|
|
50
|
+
absPath,
|
|
51
|
+
diff: '',
|
|
52
|
+
content,
|
|
53
|
+
isNewFile: true,
|
|
54
|
+
isBinary: false,
|
|
55
|
+
insertions: lineCount,
|
|
56
|
+
deletions: 0,
|
|
57
|
+
language,
|
|
58
|
+
staged: !!query.staged,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return c.json(
|
|
63
|
+
{
|
|
64
|
+
status: 'error',
|
|
65
|
+
error:
|
|
66
|
+
error instanceof Error ? error.message : 'Failed to read file',
|
|
67
|
+
},
|
|
68
|
+
500,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const diffArgs = query.staged
|
|
74
|
+
? ['diff', '--cached', '--', query.file]
|
|
75
|
+
: ['diff', '--', query.file];
|
|
76
|
+
const numstatArgs = query.staged
|
|
77
|
+
? ['diff', '--cached', '--numstat', '--', query.file]
|
|
78
|
+
: ['diff', '--numstat', '--', query.file];
|
|
79
|
+
|
|
80
|
+
const [{ stdout: diffOutput }, { stdout: numstatOutput }] =
|
|
81
|
+
await Promise.all([
|
|
82
|
+
execFileAsync('git', diffArgs, { cwd: gitRoot }),
|
|
83
|
+
execFileAsync('git', numstatArgs, { cwd: gitRoot }),
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
let insertions = 0;
|
|
87
|
+
let deletions = 0;
|
|
88
|
+
let binary = false;
|
|
89
|
+
|
|
90
|
+
const numstatLine = numstatOutput.trim().split('\n').find(Boolean);
|
|
91
|
+
if (numstatLine) {
|
|
92
|
+
const [rawInsertions, rawDeletions] = numstatLine.split('\t');
|
|
93
|
+
if (rawInsertions === '-' || rawDeletions === '-') {
|
|
94
|
+
binary = true;
|
|
95
|
+
} else {
|
|
96
|
+
insertions = Number.parseInt(rawInsertions, 10) || 0;
|
|
97
|
+
deletions = Number.parseInt(rawDeletions, 10) || 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const diffText = diffOutput ?? '';
|
|
102
|
+
if (!binary) {
|
|
103
|
+
const summary = summarizeDiff(diffText);
|
|
104
|
+
binary = summary.binary;
|
|
105
|
+
if (insertions === 0 && deletions === 0) {
|
|
106
|
+
insertions = summary.insertions;
|
|
107
|
+
deletions = summary.deletions;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const language = inferLanguage(query.file);
|
|
112
|
+
|
|
113
|
+
return c.json({
|
|
114
|
+
status: 'ok',
|
|
115
|
+
data: {
|
|
116
|
+
file: query.file,
|
|
117
|
+
absPath,
|
|
118
|
+
diff: diffText,
|
|
119
|
+
isNewFile: false,
|
|
120
|
+
isBinary: binary,
|
|
121
|
+
insertions,
|
|
122
|
+
deletions,
|
|
123
|
+
language,
|
|
124
|
+
staged: !!query.staged,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
} catch (error) {
|
|
128
|
+
return c.json(
|
|
129
|
+
{
|
|
130
|
+
status: 'error',
|
|
131
|
+
error: error instanceof Error ? error.message : 'Failed to get diff',
|
|
132
|
+
},
|
|
133
|
+
500,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Hono } from 'hono';
|
|
2
|
+
import { registerStatusRoute } from './status.ts';
|
|
3
|
+
import { registerBranchRoute } from './branch.ts';
|
|
4
|
+
import { registerDiffRoute } from './diff.ts';
|
|
5
|
+
import { registerStagingRoutes } from './staging.ts';
|
|
6
|
+
import { registerCommitRoutes } from './commit.ts';
|
|
7
|
+
import { registerPushRoute } from './push.ts';
|
|
8
|
+
|
|
9
|
+
export type { GitFile } from './types.ts';
|
|
10
|
+
|
|
11
|
+
export function registerGitRoutes(app: Hono) {
|
|
12
|
+
registerStatusRoute(app);
|
|
13
|
+
registerBranchRoute(app);
|
|
14
|
+
registerDiffRoute(app);
|
|
15
|
+
registerStagingRoutes(app);
|
|
16
|
+
registerCommitRoutes(app);
|
|
17
|
+
registerPushRoute(app);
|
|
18
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { Hono } from 'hono';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { gitPushSchema } from './schemas.ts';
|
|
5
|
+
import { validateAndGetGitRoot, getCurrentBranch } from './utils.ts';
|
|
6
|
+
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
|
|
9
|
+
export function registerPushRoute(app: Hono) {
|
|
10
|
+
app.post('/v1/git/push', async (c) => {
|
|
11
|
+
try {
|
|
12
|
+
let body = {};
|
|
13
|
+
try {
|
|
14
|
+
body = await c.req.json();
|
|
15
|
+
} catch (jsonError) {
|
|
16
|
+
console.warn(
|
|
17
|
+
'Failed to parse JSON body for git push, using empty object:',
|
|
18
|
+
jsonError,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { project } = gitPushSchema.parse(body);
|
|
23
|
+
|
|
24
|
+
const requestedPath = project || process.cwd();
|
|
25
|
+
|
|
26
|
+
const validation = await validateAndGetGitRoot(requestedPath);
|
|
27
|
+
if ('error' in validation) {
|
|
28
|
+
return c.json(
|
|
29
|
+
{ status: 'error', error: validation.error, code: validation.code },
|
|
30
|
+
400,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { gitRoot } = validation;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const { stdout: remotes } = await execFileAsync('git', ['remote'], {
|
|
38
|
+
cwd: gitRoot,
|
|
39
|
+
});
|
|
40
|
+
if (!remotes.trim()) {
|
|
41
|
+
return c.json(
|
|
42
|
+
{ status: 'error', error: 'No remote repository configured' },
|
|
43
|
+
400,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
return c.json(
|
|
48
|
+
{ status: 'error', error: 'No remote repository configured' },
|
|
49
|
+
400,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const branch = await getCurrentBranch(gitRoot);
|
|
54
|
+
let hasUpstream = false;
|
|
55
|
+
try {
|
|
56
|
+
await execFileAsync(
|
|
57
|
+
'git',
|
|
58
|
+
['rev-parse', '--abbrev-ref', '@{upstream}'],
|
|
59
|
+
{
|
|
60
|
+
cwd: gitRoot,
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
hasUpstream = true;
|
|
64
|
+
} catch {}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
let pushOutput: string;
|
|
68
|
+
let pushError: string;
|
|
69
|
+
|
|
70
|
+
if (hasUpstream) {
|
|
71
|
+
const result = await execFileAsync('git', ['push'], { cwd: gitRoot });
|
|
72
|
+
pushOutput = result.stdout;
|
|
73
|
+
pushError = result.stderr;
|
|
74
|
+
} else {
|
|
75
|
+
const result = await execFileAsync(
|
|
76
|
+
'git',
|
|
77
|
+
['push', '--set-upstream', 'origin', branch],
|
|
78
|
+
{ cwd: gitRoot },
|
|
79
|
+
);
|
|
80
|
+
pushOutput = result.stdout;
|
|
81
|
+
pushError = result.stderr;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return c.json({
|
|
85
|
+
status: 'ok',
|
|
86
|
+
data: {
|
|
87
|
+
output: pushOutput.trim() || pushError.trim(),
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
} catch (pushErr: unknown) {
|
|
91
|
+
const error = pushErr as {
|
|
92
|
+
message?: string;
|
|
93
|
+
stderr?: string;
|
|
94
|
+
code?: number;
|
|
95
|
+
};
|
|
96
|
+
const errorMessage = error.stderr || error.message || 'Failed to push';
|
|
97
|
+
|
|
98
|
+
if (
|
|
99
|
+
errorMessage.includes('failed to push') ||
|
|
100
|
+
errorMessage.includes('rejected')
|
|
101
|
+
) {
|
|
102
|
+
return c.json(
|
|
103
|
+
{
|
|
104
|
+
status: 'error',
|
|
105
|
+
error: 'Push rejected. Try pulling changes first with: git pull',
|
|
106
|
+
details: errorMessage,
|
|
107
|
+
},
|
|
108
|
+
400,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (
|
|
113
|
+
errorMessage.includes('Permission denied') ||
|
|
114
|
+
errorMessage.includes('authentication') ||
|
|
115
|
+
errorMessage.includes('could not read')
|
|
116
|
+
) {
|
|
117
|
+
return c.json(
|
|
118
|
+
{
|
|
119
|
+
status: 'error',
|
|
120
|
+
error: 'Authentication failed. Check your git credentials',
|
|
121
|
+
details: errorMessage,
|
|
122
|
+
},
|
|
123
|
+
401,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (
|
|
128
|
+
errorMessage.includes('Could not resolve host') ||
|
|
129
|
+
errorMessage.includes('network')
|
|
130
|
+
) {
|
|
131
|
+
return c.json(
|
|
132
|
+
{
|
|
133
|
+
status: 'error',
|
|
134
|
+
error: 'Network error. Check your internet connection',
|
|
135
|
+
details: errorMessage,
|
|
136
|
+
},
|
|
137
|
+
503,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return c.json(
|
|
142
|
+
{
|
|
143
|
+
status: 'error',
|
|
144
|
+
error: 'Failed to push commits',
|
|
145
|
+
details: errorMessage,
|
|
146
|
+
},
|
|
147
|
+
500,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return c.json(
|
|
152
|
+
{
|
|
153
|
+
status: 'error',
|
|
154
|
+
error: error instanceof Error ? error.message : 'Failed to push',
|
|
155
|
+
},
|
|
156
|
+
500,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const gitStatusSchema = z.object({
|
|
4
|
+
project: z.string().optional(),
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
export const gitDiffSchema = z.object({
|
|
8
|
+
project: z.string().optional(),
|
|
9
|
+
file: z.string(),
|
|
10
|
+
staged: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.transform((val) => val === 'true'),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const gitStageSchema = z.object({
|
|
17
|
+
project: z.string().optional(),
|
|
18
|
+
files: z.array(z.string()),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const gitUnstageSchema = z.object({
|
|
22
|
+
project: z.string().optional(),
|
|
23
|
+
files: z.array(z.string()),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const gitRestoreSchema = z.object({
|
|
27
|
+
project: z.string().optional(),
|
|
28
|
+
files: z.array(z.string()),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const gitDeleteSchema = z.object({
|
|
32
|
+
project: z.string().optional(),
|
|
33
|
+
files: z.array(z.string()),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const gitCommitSchema = z.object({
|
|
37
|
+
project: z.string().optional(),
|
|
38
|
+
message: z.string().min(1),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const gitGenerateCommitMessageSchema = z.object({
|
|
42
|
+
project: z.string().optional(),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const gitPushSchema = z.object({
|
|
46
|
+
project: z.string().optional(),
|
|
47
|
+
});
|