@ottocode/server 0.1.260 → 0.1.262

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 (67) hide show
  1. package/package.json +4 -3
  2. package/src/index.ts +5 -4
  3. package/src/openapi/register.ts +92 -0
  4. package/src/openapi/route.ts +22 -0
  5. package/src/routes/ask.ts +210 -99
  6. package/src/routes/auth.ts +1701 -626
  7. package/src/routes/branch.ts +281 -90
  8. package/src/routes/config/agents.ts +79 -32
  9. package/src/routes/config/cwd.ts +46 -14
  10. package/src/routes/config/debug.ts +159 -30
  11. package/src/routes/config/defaults.ts +182 -64
  12. package/src/routes/config/main.ts +109 -73
  13. package/src/routes/config/models.ts +304 -137
  14. package/src/routes/config/providers.ts +462 -166
  15. package/src/routes/config/utils.ts +2 -2
  16. package/src/routes/doctor.ts +395 -161
  17. package/src/routes/files.ts +650 -260
  18. package/src/routes/git/branch.ts +143 -52
  19. package/src/routes/git/commit.ts +347 -141
  20. package/src/routes/git/diff.ts +239 -116
  21. package/src/routes/git/init.ts +103 -23
  22. package/src/routes/git/pull.ts +167 -65
  23. package/src/routes/git/push.ts +222 -117
  24. package/src/routes/git/remote.ts +401 -100
  25. package/src/routes/git/staging.ts +502 -141
  26. package/src/routes/git/status.ts +171 -78
  27. package/src/routes/mcp.ts +1129 -404
  28. package/src/routes/openapi.ts +27 -4
  29. package/src/routes/ottorouter.ts +1221 -389
  30. package/src/routes/provider-usage.ts +153 -36
  31. package/src/routes/research.ts +817 -370
  32. package/src/routes/root.ts +50 -6
  33. package/src/routes/session-approval.ts +228 -54
  34. package/src/routes/session-files.ts +265 -134
  35. package/src/routes/session-messages.ts +330 -150
  36. package/src/routes/session-stream.ts +83 -2
  37. package/src/routes/sessions.ts +1830 -780
  38. package/src/routes/skills.ts +849 -161
  39. package/src/routes/terminals.ts +469 -103
  40. package/src/routes/tunnel.ts +394 -118
  41. package/src/runtime/ask/service.ts +1 -0
  42. package/src/runtime/message/compaction-limits.ts +3 -3
  43. package/src/runtime/provider/reasoning.ts +2 -1
  44. package/src/runtime/session/db-operations.ts +4 -3
  45. package/src/runtime/utils/token.ts +7 -2
  46. package/src/tools/adapter.ts +21 -0
  47. package/src/openapi/paths/ask.ts +0 -81
  48. package/src/openapi/paths/auth.ts +0 -687
  49. package/src/openapi/paths/branch.ts +0 -102
  50. package/src/openapi/paths/config.ts +0 -485
  51. package/src/openapi/paths/doctor.ts +0 -165
  52. package/src/openapi/paths/files.ts +0 -236
  53. package/src/openapi/paths/git.ts +0 -690
  54. package/src/openapi/paths/mcp.ts +0 -339
  55. package/src/openapi/paths/messages.ts +0 -103
  56. package/src/openapi/paths/ottorouter.ts +0 -594
  57. package/src/openapi/paths/provider-usage.ts +0 -59
  58. package/src/openapi/paths/research.ts +0 -227
  59. package/src/openapi/paths/session-approval.ts +0 -93
  60. package/src/openapi/paths/session-extras.ts +0 -336
  61. package/src/openapi/paths/session-files.ts +0 -91
  62. package/src/openapi/paths/sessions.ts +0 -210
  63. package/src/openapi/paths/skills.ts +0 -377
  64. package/src/openapi/paths/stream.ts +0 -26
  65. package/src/openapi/paths/terminals.ts +0 -226
  66. package/src/openapi/paths/tunnel.ts +0 -163
  67. package/src/openapi/spec.ts +0 -73
@@ -3,96 +3,198 @@ import { execFile } from 'node:child_process';
3
3
  import { promisify } from 'node:util';
4
4
  import { gitPullSchema } from './schemas.ts';
5
5
  import { validateAndGetGitRoot } from './utils.ts';
6
+ import { openApiRoute } from '../../openapi/route.ts';
6
7
 
7
8
  const execFileAsync = promisify(execFile);
8
9
 
9
10
  export function registerPullRoute(app: Hono) {
10
- app.post('/v1/git/pull', async (c) => {
11
- try {
12
- let body = {};
11
+ openApiRoute(
12
+ app,
13
+ {
14
+ method: 'post',
15
+ path: '/v1/git/pull',
16
+ tags: ['git'],
17
+ operationId: 'pullChanges',
18
+ summary: 'Pull changes from remote',
19
+ description: 'Pulls changes from the configured remote repository',
20
+ requestBody: {
21
+ required: false,
22
+ content: {
23
+ 'application/json': {
24
+ schema: {
25
+ type: 'object',
26
+ properties: {
27
+ project: {
28
+ type: 'string',
29
+ },
30
+ },
31
+ },
32
+ },
33
+ },
34
+ },
35
+ responses: {
36
+ '200': {
37
+ description: 'OK',
38
+ content: {
39
+ 'application/json': {
40
+ schema: {
41
+ type: 'object',
42
+ properties: {
43
+ status: {
44
+ type: 'string',
45
+ enum: ['ok'],
46
+ },
47
+ data: {
48
+ type: 'object',
49
+ properties: {
50
+ output: {
51
+ type: 'string',
52
+ },
53
+ },
54
+ required: ['output'],
55
+ },
56
+ },
57
+ required: ['status', 'data'],
58
+ },
59
+ },
60
+ },
61
+ },
62
+ '400': {
63
+ description: 'Error',
64
+ content: {
65
+ 'application/json': {
66
+ schema: {
67
+ type: 'object',
68
+ properties: {
69
+ status: {
70
+ type: 'string',
71
+ enum: ['error'],
72
+ },
73
+ error: {
74
+ type: 'string',
75
+ },
76
+ code: {
77
+ type: 'string',
78
+ },
79
+ },
80
+ required: ['status', 'error'],
81
+ },
82
+ },
83
+ },
84
+ },
85
+ '500': {
86
+ description: 'Error',
87
+ content: {
88
+ 'application/json': {
89
+ schema: {
90
+ type: 'object',
91
+ properties: {
92
+ status: {
93
+ type: 'string',
94
+ enum: ['error'],
95
+ },
96
+ error: {
97
+ type: 'string',
98
+ },
99
+ code: {
100
+ type: 'string',
101
+ },
102
+ },
103
+ required: ['status', 'error'],
104
+ },
105
+ },
106
+ },
107
+ },
108
+ },
109
+ },
110
+ async (c) => {
13
111
  try {
14
- body = await c.req.json();
15
- } catch {
16
- body = {};
17
- }
18
-
19
- const { project } = gitPullSchema.parse(body);
20
-
21
- const requestedPath = project || process.cwd();
22
-
23
- const validation = await validateAndGetGitRoot(requestedPath);
24
- if ('error' in validation) {
25
- return c.json(
26
- { status: 'error', error: validation.error, code: validation.code },
27
- 400,
28
- );
29
- }
112
+ let body = {};
113
+ try {
114
+ body = await c.req.json();
115
+ } catch {
116
+ body = {};
117
+ }
30
118
 
31
- const { gitRoot } = validation;
119
+ const { project } = gitPullSchema.parse(body);
32
120
 
33
- try {
34
- const result = await execFileAsync('git', ['pull'], {
35
- cwd: gitRoot,
36
- });
37
-
38
- return c.json({
39
- status: 'ok',
40
- data: {
41
- output: result.stdout.trim() || result.stderr.trim(),
42
- },
43
- });
44
- } catch (pullErr: unknown) {
45
- const error = pullErr as {
46
- message?: string;
47
- stderr?: string;
48
- };
49
- const errorMessage = error.stderr || error.message || 'Failed to pull';
121
+ const requestedPath = project || process.cwd();
50
122
 
51
- if (
52
- errorMessage.includes('CONFLICT') ||
53
- errorMessage.includes('merge conflict')
54
- ) {
123
+ const validation = await validateAndGetGitRoot(requestedPath);
124
+ if ('error' in validation) {
55
125
  return c.json(
56
- {
57
- status: 'error',
58
- error: 'Merge conflicts detected. Resolve conflicts manually',
59
- details: errorMessage,
60
- },
126
+ { status: 'error', error: validation.error, code: validation.code },
61
127
  400,
62
128
  );
63
129
  }
64
130
 
65
- if (
66
- errorMessage.includes('Permission denied') ||
67
- errorMessage.includes('authentication')
68
- ) {
131
+ const { gitRoot } = validation;
132
+
133
+ try {
134
+ const result = await execFileAsync('git', ['pull'], {
135
+ cwd: gitRoot,
136
+ });
137
+
138
+ return c.json({
139
+ status: 'ok',
140
+ data: {
141
+ output: result.stdout.trim() || result.stderr.trim(),
142
+ },
143
+ });
144
+ } catch (pullErr: unknown) {
145
+ const error = pullErr as {
146
+ message?: string;
147
+ stderr?: string;
148
+ };
149
+ const errorMessage =
150
+ error.stderr || error.message || 'Failed to pull';
151
+
152
+ if (
153
+ errorMessage.includes('CONFLICT') ||
154
+ errorMessage.includes('merge conflict')
155
+ ) {
156
+ return c.json(
157
+ {
158
+ status: 'error',
159
+ error: 'Merge conflicts detected. Resolve conflicts manually',
160
+ details: errorMessage,
161
+ },
162
+ 400,
163
+ );
164
+ }
165
+
166
+ if (
167
+ errorMessage.includes('Permission denied') ||
168
+ errorMessage.includes('authentication')
169
+ ) {
170
+ return c.json(
171
+ {
172
+ status: 'error',
173
+ error: 'Authentication failed. Check your git credentials',
174
+ details: errorMessage,
175
+ },
176
+ 401,
177
+ );
178
+ }
179
+
69
180
  return c.json(
70
181
  {
71
182
  status: 'error',
72
- error: 'Authentication failed. Check your git credentials',
183
+ error: 'Failed to pull changes',
73
184
  details: errorMessage,
74
185
  },
75
- 401,
186
+ 500,
76
187
  );
77
188
  }
78
-
189
+ } catch (error) {
79
190
  return c.json(
80
191
  {
81
192
  status: 'error',
82
- error: 'Failed to pull changes',
83
- details: errorMessage,
193
+ error: error instanceof Error ? error.message : 'Failed to pull',
84
194
  },
85
195
  500,
86
196
  );
87
197
  }
88
- } catch (error) {
89
- return c.json(
90
- {
91
- status: 'error',
92
- error: error instanceof Error ? error.message : 'Failed to pull',
93
- },
94
- 500,
95
- );
96
- }
97
- });
198
+ },
199
+ );
98
200
  }
@@ -3,158 +3,263 @@ import { execFile } from 'node:child_process';
3
3
  import { promisify } from 'node:util';
4
4
  import { gitPushSchema } from './schemas.ts';
5
5
  import { validateAndGetGitRoot, getCurrentBranch } from './utils.ts';
6
+ import { openApiRoute } from '../../openapi/route.ts';
6
7
 
7
8
  const execFileAsync = promisify(execFile);
8
9
 
9
10
  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
-
11
+ openApiRoute(
12
+ app,
13
+ {
14
+ method: 'post',
15
+ path: '/v1/git/push',
16
+ tags: ['git'],
17
+ operationId: 'pushCommits',
18
+ summary: 'Push commits to remote',
19
+ description: 'Pushes local commits to the configured remote repository',
20
+ requestBody: {
21
+ required: false,
22
+ content: {
23
+ 'application/json': {
24
+ schema: {
25
+ type: 'object',
26
+ properties: {
27
+ project: {
28
+ type: 'string',
29
+ },
30
+ },
31
+ },
32
+ },
33
+ },
34
+ },
35
+ responses: {
36
+ '200': {
37
+ description: 'OK',
38
+ content: {
39
+ 'application/json': {
40
+ schema: {
41
+ type: 'object',
42
+ properties: {
43
+ status: {
44
+ type: 'string',
45
+ enum: ['ok'],
46
+ },
47
+ data: {
48
+ type: 'object',
49
+ properties: {
50
+ output: {
51
+ type: 'string',
52
+ },
53
+ },
54
+ required: ['output'],
55
+ },
56
+ },
57
+ required: ['status', 'data'],
58
+ },
59
+ },
60
+ },
61
+ },
62
+ '400': {
63
+ description: 'Error',
64
+ content: {
65
+ 'application/json': {
66
+ schema: {
67
+ type: 'object',
68
+ properties: {
69
+ status: {
70
+ type: 'string',
71
+ enum: ['error'],
72
+ },
73
+ error: {
74
+ type: 'string',
75
+ },
76
+ code: {
77
+ type: 'string',
78
+ },
79
+ },
80
+ required: ['status', 'error'],
81
+ },
82
+ },
83
+ },
84
+ },
85
+ '500': {
86
+ description: 'Error',
87
+ content: {
88
+ 'application/json': {
89
+ schema: {
90
+ type: 'object',
91
+ properties: {
92
+ status: {
93
+ type: 'string',
94
+ enum: ['error'],
95
+ },
96
+ error: {
97
+ type: 'string',
98
+ },
99
+ code: {
100
+ type: 'string',
101
+ },
102
+ },
103
+ required: ['status', 'error'],
104
+ },
105
+ },
106
+ },
107
+ },
108
+ },
109
+ },
110
+ async (c) => {
36
111
  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,
112
+ let body = {};
113
+ try {
114
+ body = await c.req.json();
115
+ } catch (jsonError) {
116
+ console.warn(
117
+ 'Failed to parse JSON body for git push, using empty object:',
118
+ jsonError,
44
119
  );
45
120
  }
46
- } catch {
47
- return c.json(
48
- { status: 'error', error: 'No remote repository configured' },
49
- 400,
50
- );
51
- }
52
121
 
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 {}
122
+ const { project } = gitPushSchema.parse(body);
65
123
 
66
- try {
67
- let pushOutput: string;
68
- let pushError: string;
124
+ const requestedPath = project || process.cwd();
69
125
 
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 },
126
+ const validation = await validateAndGetGitRoot(requestedPath);
127
+ if ('error' in validation) {
128
+ return c.json(
129
+ { status: 'error', error: validation.error, code: validation.code },
130
+ 400,
79
131
  );
80
- pushOutput = result.stdout;
81
- pushError = result.stderr;
82
132
  }
83
133
 
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';
134
+ const { gitRoot } = validation;
97
135
 
98
- if (
99
- errorMessage.includes('failed to push') ||
100
- errorMessage.includes('rejected')
101
- ) {
136
+ try {
137
+ const { stdout: remotes } = await execFileAsync('git', ['remote'], {
138
+ cwd: gitRoot,
139
+ });
140
+ if (!remotes.trim()) {
141
+ return c.json(
142
+ { status: 'error', error: 'No remote repository configured' },
143
+ 400,
144
+ );
145
+ }
146
+ } catch {
102
147
  return c.json(
103
- {
104
- status: 'error',
105
- error: 'Push rejected. Try pulling changes first with: git pull',
106
- details: errorMessage,
107
- },
148
+ { status: 'error', error: 'No remote repository configured' },
108
149
  400,
109
150
  );
110
151
  }
111
152
 
112
- if (
113
- errorMessage.includes('Permission denied') ||
114
- errorMessage.includes('authentication') ||
115
- errorMessage.includes('could not read')
116
- ) {
117
- return c.json(
153
+ const branch = await getCurrentBranch(gitRoot);
154
+ let hasUpstream = false;
155
+ try {
156
+ await execFileAsync(
157
+ 'git',
158
+ ['rev-parse', '--abbrev-ref', '@{upstream}'],
118
159
  {
119
- status: 'error',
120
- error: 'Authentication failed. Check your git credentials',
121
- details: errorMessage,
160
+ cwd: gitRoot,
122
161
  },
123
- 401,
124
162
  );
125
- }
163
+ hasUpstream = true;
164
+ } catch {}
165
+
166
+ try {
167
+ let pushOutput: string;
168
+ let pushError: string;
169
+
170
+ if (hasUpstream) {
171
+ const result = await execFileAsync('git', ['push'], {
172
+ cwd: gitRoot,
173
+ });
174
+ pushOutput = result.stdout;
175
+ pushError = result.stderr;
176
+ } else {
177
+ const result = await execFileAsync(
178
+ 'git',
179
+ ['push', '--set-upstream', 'origin', branch],
180
+ { cwd: gitRoot },
181
+ );
182
+ pushOutput = result.stdout;
183
+ pushError = result.stderr;
184
+ }
185
+
186
+ return c.json({
187
+ status: 'ok',
188
+ data: {
189
+ output: pushOutput.trim() || pushError.trim(),
190
+ },
191
+ });
192
+ } catch (pushErr: unknown) {
193
+ const error = pushErr as {
194
+ message?: string;
195
+ stderr?: string;
196
+ code?: number;
197
+ };
198
+ const errorMessage =
199
+ error.stderr || error.message || 'Failed to push';
200
+
201
+ if (
202
+ errorMessage.includes('failed to push') ||
203
+ errorMessage.includes('rejected')
204
+ ) {
205
+ return c.json(
206
+ {
207
+ status: 'error',
208
+ error:
209
+ 'Push rejected. Try pulling changes first with: git pull',
210
+ details: errorMessage,
211
+ },
212
+ 400,
213
+ );
214
+ }
215
+
216
+ if (
217
+ errorMessage.includes('Permission denied') ||
218
+ errorMessage.includes('authentication') ||
219
+ errorMessage.includes('could not read')
220
+ ) {
221
+ return c.json(
222
+ {
223
+ status: 'error',
224
+ error: 'Authentication failed. Check your git credentials',
225
+ details: errorMessage,
226
+ },
227
+ 401,
228
+ );
229
+ }
230
+
231
+ if (
232
+ errorMessage.includes('Could not resolve host') ||
233
+ errorMessage.includes('network')
234
+ ) {
235
+ return c.json(
236
+ {
237
+ status: 'error',
238
+ error: 'Network error. Check your internet connection',
239
+ details: errorMessage,
240
+ },
241
+ 503,
242
+ );
243
+ }
126
244
 
127
- if (
128
- errorMessage.includes('Could not resolve host') ||
129
- errorMessage.includes('network')
130
- ) {
131
245
  return c.json(
132
246
  {
133
247
  status: 'error',
134
- error: 'Network error. Check your internet connection',
248
+ error: 'Failed to push commits',
135
249
  details: errorMessage,
136
250
  },
137
- 503,
251
+ 500,
138
252
  );
139
253
  }
140
-
254
+ } catch (error) {
141
255
  return c.json(
142
256
  {
143
257
  status: 'error',
144
- error: 'Failed to push commits',
145
- details: errorMessage,
258
+ error: error instanceof Error ? error.message : 'Failed to push',
146
259
  },
147
260
  500,
148
261
  );
149
262
  }
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
- });
263
+ },
264
+ );
160
265
  }