@ottocode/server 0.1.260 → 0.1.261

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
@@ -10,135 +10,258 @@ import {
10
10
  inferLanguage,
11
11
  summarizeDiff,
12
12
  } from './utils.ts';
13
+ import { openApiRoute } from '../../openapi/route.ts';
13
14
 
14
15
  const execFileAsync = promisify(execFile);
15
16
 
16
17
  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,
18
+ openApiRoute(
19
+ app,
20
+ {
21
+ method: 'get',
22
+ path: '/v1/git/diff',
23
+ tags: ['git'],
24
+ operationId: 'getGitDiff',
25
+ summary: 'Get git diff for a file',
26
+ parameters: [
27
+ {
28
+ in: 'query',
29
+ name: 'project',
30
+ required: false,
31
+ schema: {
32
+ type: 'string',
33
+ },
34
+ description:
35
+ 'Project root override (defaults to current working directory).',
36
+ },
37
+ {
38
+ in: 'query',
39
+ name: 'file',
40
+ required: true,
41
+ schema: {
42
+ type: 'string',
43
+ },
44
+ description: 'File path to get diff for',
45
+ },
46
+ {
47
+ in: 'query',
48
+ name: 'staged',
49
+ required: false,
50
+ schema: {
51
+ type: 'string',
52
+ enum: ['true', 'false'],
53
+ },
54
+ description: 'Show staged diff (default: unstaged)',
55
+ },
56
+ {
57
+ in: 'query',
58
+ name: 'fullFile',
59
+ required: false,
60
+ schema: {
61
+ type: 'string',
62
+ enum: ['true', 'false'],
63
+ },
64
+ description: 'Include full file content in diff',
65
+ },
66
+ ],
67
+ responses: {
68
+ '200': {
69
+ description: 'OK',
70
+ content: {
71
+ 'application/json': {
72
+ schema: {
73
+ type: 'object',
74
+ properties: {
75
+ status: {
76
+ type: 'string',
77
+ enum: ['ok'],
78
+ },
79
+ data: {
80
+ $ref: '#/components/schemas/GitDiff',
81
+ },
82
+ },
83
+ required: ['status', 'data'],
84
+ },
59
85
  },
60
- });
61
- } catch (error) {
62
- return c.json(
63
- {
64
- status: 'error',
65
- error:
66
- error instanceof Error ? error.message : 'Failed to read file',
86
+ },
87
+ },
88
+ '400': {
89
+ description: 'Error',
90
+ content: {
91
+ 'application/json': {
92
+ schema: {
93
+ type: 'object',
94
+ properties: {
95
+ status: {
96
+ type: 'string',
97
+ enum: ['error'],
98
+ },
99
+ error: {
100
+ type: 'string',
101
+ },
102
+ code: {
103
+ type: 'string',
104
+ },
105
+ },
106
+ required: ['status', 'error'],
107
+ },
108
+ },
109
+ },
110
+ },
111
+ '500': {
112
+ description: 'Error',
113
+ content: {
114
+ 'application/json': {
115
+ schema: {
116
+ type: 'object',
117
+ properties: {
118
+ status: {
119
+ type: 'string',
120
+ enum: ['error'],
121
+ },
122
+ error: {
123
+ type: 'string',
124
+ },
125
+ code: {
126
+ type: 'string',
127
+ },
128
+ },
129
+ required: ['status', 'error'],
130
+ },
67
131
  },
68
- 500,
132
+ },
133
+ },
134
+ },
135
+ },
136
+ async (c) => {
137
+ try {
138
+ const query = gitDiffSchema.parse({
139
+ project: c.req.query('project'),
140
+ file: c.req.query('file'),
141
+ staged: c.req.query('staged'),
142
+ });
143
+
144
+ const requestedPath = query.project || process.cwd();
145
+
146
+ const validation = await validateAndGetGitRoot(requestedPath);
147
+ if ('error' in validation) {
148
+ return c.json(
149
+ { status: 'error', error: validation.error, code: validation.code },
150
+ 400,
69
151
  );
70
152
  }
71
- }
72
153
 
73
- const diffArgs = query.staged
74
- ? ['diff', '--cached', '--', query.file]
75
- : ['diff', '--', query.file];
154
+ const { gitRoot } = validation;
155
+ const absPath = join(gitRoot, query.file);
76
156
 
77
- const fullFile = c.req.query('fullFile') === 'true';
78
- if (fullFile) {
79
- diffArgs.splice(1, 0, '-U99999');
80
- }
157
+ const isNewFile = await checkIfNewFile(gitRoot, query.file);
158
+
159
+ if (isNewFile) {
160
+ try {
161
+ const content = await readFile(absPath, 'utf-8');
162
+ const lineCount = content.split('\n').length;
163
+ const language = inferLanguage(query.file);
81
164
 
82
- const numstatArgs = query.staged
83
- ? ['diff', '--cached', '--numstat', '--', query.file]
84
- : ['diff', '--numstat', '--', query.file];
85
-
86
- const [{ stdout: diffOutput }, { stdout: numstatOutput }] =
87
- await Promise.all([
88
- execFileAsync('git', diffArgs, { cwd: gitRoot }),
89
- execFileAsync('git', numstatArgs, { cwd: gitRoot }),
90
- ]);
91
-
92
- let insertions = 0;
93
- let deletions = 0;
94
- let binary = false;
95
-
96
- const numstatLine = numstatOutput.trim().split('\n').find(Boolean);
97
- if (numstatLine) {
98
- const [rawInsertions, rawDeletions] = numstatLine.split('\t');
99
- if (rawInsertions === '-' || rawDeletions === '-') {
100
- binary = true;
101
- } else {
102
- insertions = Number.parseInt(rawInsertions, 10) || 0;
103
- deletions = Number.parseInt(rawDeletions, 10) || 0;
165
+ return c.json({
166
+ status: 'ok',
167
+ data: {
168
+ file: query.file,
169
+ absPath,
170
+ diff: '',
171
+ content,
172
+ isNewFile: true,
173
+ isBinary: false,
174
+ insertions: lineCount,
175
+ deletions: 0,
176
+ language,
177
+ staged: !!query.staged,
178
+ },
179
+ });
180
+ } catch (error) {
181
+ return c.json(
182
+ {
183
+ status: 'error',
184
+ error:
185
+ error instanceof Error
186
+ ? error.message
187
+ : 'Failed to read file',
188
+ },
189
+ 500,
190
+ );
191
+ }
104
192
  }
105
- }
106
193
 
107
- const diffText = diffOutput ?? '';
108
- if (!binary) {
109
- const summary = summarizeDiff(diffText);
110
- binary = summary.binary;
111
- if (insertions === 0 && deletions === 0) {
112
- insertions = summary.insertions;
113
- deletions = summary.deletions;
194
+ const diffArgs = query.staged
195
+ ? ['diff', '--cached', '--', query.file]
196
+ : ['diff', '--', query.file];
197
+
198
+ const fullFile = c.req.query('fullFile') === 'true';
199
+ if (fullFile) {
200
+ diffArgs.splice(1, 0, '-U99999');
114
201
  }
115
- }
116
202
 
117
- const language = inferLanguage(query.file);
118
-
119
- return c.json({
120
- status: 'ok',
121
- data: {
122
- file: query.file,
123
- absPath,
124
- diff: diffText,
125
- fullFile,
126
- isNewFile: false,
127
- isBinary: binary,
128
- insertions,
129
- deletions,
130
- language,
131
- staged: !!query.staged,
132
- },
133
- });
134
- } catch (error) {
135
- return c.json(
136
- {
137
- status: 'error',
138
- error: error instanceof Error ? error.message : 'Failed to get diff',
139
- },
140
- 500,
141
- );
142
- }
143
- });
203
+ const numstatArgs = query.staged
204
+ ? ['diff', '--cached', '--numstat', '--', query.file]
205
+ : ['diff', '--numstat', '--', query.file];
206
+
207
+ const [{ stdout: diffOutput }, { stdout: numstatOutput }] =
208
+ await Promise.all([
209
+ execFileAsync('git', diffArgs, { cwd: gitRoot }),
210
+ execFileAsync('git', numstatArgs, { cwd: gitRoot }),
211
+ ]);
212
+
213
+ let insertions = 0;
214
+ let deletions = 0;
215
+ let binary = false;
216
+
217
+ const numstatLine = numstatOutput.trim().split('\n').find(Boolean);
218
+ if (numstatLine) {
219
+ const [rawInsertions, rawDeletions] = numstatLine.split('\t');
220
+ if (rawInsertions === '-' || rawDeletions === '-') {
221
+ binary = true;
222
+ } else {
223
+ insertions = Number.parseInt(rawInsertions, 10) || 0;
224
+ deletions = Number.parseInt(rawDeletions, 10) || 0;
225
+ }
226
+ }
227
+
228
+ const diffText = diffOutput ?? '';
229
+ if (!binary) {
230
+ const summary = summarizeDiff(diffText);
231
+ binary = summary.binary;
232
+ if (insertions === 0 && deletions === 0) {
233
+ insertions = summary.insertions;
234
+ deletions = summary.deletions;
235
+ }
236
+ }
237
+
238
+ const language = inferLanguage(query.file);
239
+
240
+ return c.json({
241
+ status: 'ok',
242
+ data: {
243
+ file: query.file,
244
+ absPath,
245
+ diff: diffText,
246
+ fullFile,
247
+ isNewFile: false,
248
+ isBinary: binary,
249
+ insertions,
250
+ deletions,
251
+ language,
252
+ staged: !!query.staged,
253
+ },
254
+ });
255
+ } catch (error) {
256
+ return c.json(
257
+ {
258
+ status: 'error',
259
+ error:
260
+ error instanceof Error ? error.message : 'Failed to get diff',
261
+ },
262
+ 500,
263
+ );
264
+ }
265
+ },
266
+ );
144
267
  }
@@ -2,33 +2,113 @@ import type { Hono } from 'hono';
2
2
  import { execFile } from 'node:child_process';
3
3
  import { promisify } from 'node:util';
4
4
  import { gitStatusSchema } from './schemas.ts';
5
+ import { openApiRoute } from '../../openapi/route.ts';
5
6
 
6
7
  const execFileAsync = promisify(execFile);
7
8
 
8
9
  export function registerInitRoute(app: Hono) {
9
- app.post('/v1/git/init', async (c) => {
10
- try {
11
- const body = await c.req.json().catch(() => ({}));
12
- const { project } = gitStatusSchema.parse(body);
13
- const requestedPath = project || process.cwd();
10
+ openApiRoute(
11
+ app,
12
+ {
13
+ method: 'post',
14
+ path: '/v1/git/init',
15
+ tags: ['git'],
16
+ operationId: 'initGitRepo',
17
+ summary: 'Initialize a git repository',
18
+ requestBody: {
19
+ required: false,
20
+ content: {
21
+ 'application/json': {
22
+ schema: {
23
+ type: 'object',
24
+ properties: {
25
+ project: {
26
+ type: 'string',
27
+ },
28
+ },
29
+ },
30
+ },
31
+ },
32
+ },
33
+ responses: {
34
+ '200': {
35
+ description: 'OK',
36
+ content: {
37
+ 'application/json': {
38
+ schema: {
39
+ type: 'object',
40
+ properties: {
41
+ status: {
42
+ type: 'string',
43
+ enum: ['ok'],
44
+ },
45
+ data: {
46
+ type: 'object',
47
+ properties: {
48
+ initialized: {
49
+ type: 'boolean',
50
+ },
51
+ path: {
52
+ type: 'string',
53
+ },
54
+ },
55
+ required: ['initialized', 'path'],
56
+ },
57
+ },
58
+ required: ['status', 'data'],
59
+ },
60
+ },
61
+ },
62
+ },
63
+ '500': {
64
+ description: 'Error',
65
+ content: {
66
+ 'application/json': {
67
+ schema: {
68
+ type: 'object',
69
+ properties: {
70
+ status: {
71
+ type: 'string',
72
+ enum: ['error'],
73
+ },
74
+ error: {
75
+ type: 'string',
76
+ },
77
+ code: {
78
+ type: 'string',
79
+ },
80
+ },
81
+ required: ['status', 'error'],
82
+ },
83
+ },
84
+ },
85
+ },
86
+ },
87
+ },
88
+ async (c) => {
89
+ try {
90
+ const body = await c.req.json().catch(() => ({}));
91
+ const { project } = gitStatusSchema.parse(body);
92
+ const requestedPath = project || process.cwd();
14
93
 
15
- await execFileAsync('git', ['init'], { cwd: requestedPath });
94
+ await execFileAsync('git', ['init'], { cwd: requestedPath });
16
95
 
17
- return c.json({
18
- status: 'ok',
19
- data: { initialized: true, path: requestedPath },
20
- });
21
- } catch (error) {
22
- return c.json(
23
- {
24
- status: 'error',
25
- error:
26
- error instanceof Error
27
- ? error.message
28
- : 'Failed to initialize git repository',
29
- },
30
- 500,
31
- );
32
- }
33
- });
96
+ return c.json({
97
+ status: 'ok',
98
+ data: { initialized: true, path: requestedPath },
99
+ });
100
+ } catch (error) {
101
+ return c.json(
102
+ {
103
+ status: 'error',
104
+ error:
105
+ error instanceof Error
106
+ ? error.message
107
+ : 'Failed to initialize git repository',
108
+ },
109
+ 500,
110
+ );
111
+ }
112
+ },
113
+ );
34
114
  }