@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.
- package/package.json +4 -3
- package/src/index.ts +5 -4
- package/src/openapi/register.ts +92 -0
- package/src/openapi/route.ts +22 -0
- package/src/routes/ask.ts +210 -99
- package/src/routes/auth.ts +1701 -626
- package/src/routes/branch.ts +281 -90
- package/src/routes/config/agents.ts +79 -32
- package/src/routes/config/cwd.ts +46 -14
- package/src/routes/config/debug.ts +159 -30
- package/src/routes/config/defaults.ts +182 -64
- package/src/routes/config/main.ts +109 -73
- package/src/routes/config/models.ts +304 -137
- package/src/routes/config/providers.ts +462 -166
- package/src/routes/config/utils.ts +2 -2
- package/src/routes/doctor.ts +395 -161
- package/src/routes/files.ts +650 -260
- package/src/routes/git/branch.ts +143 -52
- package/src/routes/git/commit.ts +347 -141
- package/src/routes/git/diff.ts +239 -116
- package/src/routes/git/init.ts +103 -23
- package/src/routes/git/pull.ts +167 -65
- package/src/routes/git/push.ts +222 -117
- package/src/routes/git/remote.ts +401 -100
- package/src/routes/git/staging.ts +502 -141
- package/src/routes/git/status.ts +171 -78
- package/src/routes/mcp.ts +1129 -404
- package/src/routes/openapi.ts +27 -4
- package/src/routes/ottorouter.ts +1221 -389
- package/src/routes/provider-usage.ts +153 -36
- package/src/routes/research.ts +817 -370
- package/src/routes/root.ts +50 -6
- package/src/routes/session-approval.ts +228 -54
- package/src/routes/session-files.ts +265 -134
- package/src/routes/session-messages.ts +330 -150
- package/src/routes/session-stream.ts +83 -2
- package/src/routes/sessions.ts +1830 -780
- package/src/routes/skills.ts +849 -161
- package/src/routes/terminals.ts +469 -103
- package/src/routes/tunnel.ts +394 -118
- package/src/runtime/ask/service.ts +1 -0
- package/src/runtime/message/compaction-limits.ts +3 -3
- package/src/runtime/provider/reasoning.ts +2 -1
- package/src/runtime/session/db-operations.ts +4 -3
- package/src/runtime/utils/token.ts +7 -2
- package/src/tools/adapter.ts +21 -0
- package/src/openapi/paths/ask.ts +0 -81
- package/src/openapi/paths/auth.ts +0 -687
- package/src/openapi/paths/branch.ts +0 -102
- package/src/openapi/paths/config.ts +0 -485
- package/src/openapi/paths/doctor.ts +0 -165
- package/src/openapi/paths/files.ts +0 -236
- package/src/openapi/paths/git.ts +0 -690
- package/src/openapi/paths/mcp.ts +0 -339
- package/src/openapi/paths/messages.ts +0 -103
- package/src/openapi/paths/ottorouter.ts +0 -594
- package/src/openapi/paths/provider-usage.ts +0 -59
- package/src/openapi/paths/research.ts +0 -227
- package/src/openapi/paths/session-approval.ts +0 -93
- package/src/openapi/paths/session-extras.ts +0 -336
- package/src/openapi/paths/session-files.ts +0 -91
- package/src/openapi/paths/sessions.ts +0 -210
- package/src/openapi/paths/skills.ts +0 -377
- package/src/openapi/paths/stream.ts +0 -26
- package/src/openapi/paths/terminals.ts +0 -226
- package/src/openapi/paths/tunnel.ts +0 -163
- package/src/openapi/spec.ts +0 -73
package/src/routes/doctor.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
logger,
|
|
16
16
|
} from '@ottocode/sdk';
|
|
17
17
|
import { serializeError } from '../runtime/errors/api-error.ts';
|
|
18
|
+
import { openApiRoute } from '../openapi/route.ts';
|
|
18
19
|
|
|
19
20
|
async function fileExists(path: string | null): Promise<boolean> {
|
|
20
21
|
if (!path) return false;
|
|
@@ -46,172 +47,405 @@ async function listDir(dir: string | null): Promise<string[]> {
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
export function registerDoctorRoutes(app: Hono) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
50
|
+
openApiRoute(
|
|
51
|
+
app,
|
|
52
|
+
{
|
|
53
|
+
method: 'get',
|
|
54
|
+
path: '/v1/doctor',
|
|
55
|
+
tags: ['config'],
|
|
56
|
+
operationId: 'runDoctor',
|
|
57
|
+
summary: 'Run diagnostics on the current configuration',
|
|
58
|
+
parameters: [
|
|
59
|
+
{
|
|
60
|
+
in: 'query',
|
|
61
|
+
name: 'project',
|
|
62
|
+
required: false,
|
|
63
|
+
schema: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
},
|
|
66
|
+
description:
|
|
67
|
+
'Project root override (defaults to current working directory).',
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
responses: {
|
|
71
|
+
'200': {
|
|
72
|
+
description: 'OK',
|
|
73
|
+
content: {
|
|
74
|
+
'application/json': {
|
|
75
|
+
schema: {
|
|
76
|
+
type: 'object',
|
|
77
|
+
properties: {
|
|
78
|
+
providers: {
|
|
79
|
+
type: 'array',
|
|
80
|
+
items: {
|
|
81
|
+
type: 'object',
|
|
82
|
+
properties: {
|
|
83
|
+
id: {
|
|
84
|
+
type: 'string',
|
|
85
|
+
},
|
|
86
|
+
ok: {
|
|
87
|
+
type: 'boolean',
|
|
88
|
+
},
|
|
89
|
+
configured: {
|
|
90
|
+
type: 'boolean',
|
|
91
|
+
},
|
|
92
|
+
sources: {
|
|
93
|
+
type: 'array',
|
|
94
|
+
items: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
required: ['id', 'ok', 'configured', 'sources'],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
defaults: {
|
|
103
|
+
type: 'object',
|
|
104
|
+
properties: {
|
|
105
|
+
agent: {
|
|
106
|
+
type: 'string',
|
|
107
|
+
},
|
|
108
|
+
provider: {
|
|
109
|
+
type: 'string',
|
|
110
|
+
},
|
|
111
|
+
model: {
|
|
112
|
+
type: 'string',
|
|
113
|
+
},
|
|
114
|
+
providerAuthorized: {
|
|
115
|
+
type: 'boolean',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
required: [
|
|
119
|
+
'agent',
|
|
120
|
+
'provider',
|
|
121
|
+
'model',
|
|
122
|
+
'providerAuthorized',
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
agents: {
|
|
126
|
+
type: 'object',
|
|
127
|
+
properties: {
|
|
128
|
+
globalPath: {
|
|
129
|
+
type: 'string',
|
|
130
|
+
nullable: true,
|
|
131
|
+
},
|
|
132
|
+
localPath: {
|
|
133
|
+
type: 'string',
|
|
134
|
+
nullable: true,
|
|
135
|
+
},
|
|
136
|
+
globalNames: {
|
|
137
|
+
type: 'array',
|
|
138
|
+
items: {
|
|
139
|
+
type: 'string',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
localNames: {
|
|
143
|
+
type: 'array',
|
|
144
|
+
items: {
|
|
145
|
+
type: 'string',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
required: [
|
|
150
|
+
'globalPath',
|
|
151
|
+
'localPath',
|
|
152
|
+
'globalNames',
|
|
153
|
+
'localNames',
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
tools: {
|
|
157
|
+
type: 'object',
|
|
158
|
+
properties: {
|
|
159
|
+
defaultNames: {
|
|
160
|
+
type: 'array',
|
|
161
|
+
items: {
|
|
162
|
+
type: 'string',
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
globalPath: {
|
|
166
|
+
type: 'string',
|
|
167
|
+
nullable: true,
|
|
168
|
+
},
|
|
169
|
+
globalNames: {
|
|
170
|
+
type: 'array',
|
|
171
|
+
items: {
|
|
172
|
+
type: 'string',
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
localPath: {
|
|
176
|
+
type: 'string',
|
|
177
|
+
nullable: true,
|
|
178
|
+
},
|
|
179
|
+
localNames: {
|
|
180
|
+
type: 'array',
|
|
181
|
+
items: {
|
|
182
|
+
type: 'string',
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
effectiveNames: {
|
|
186
|
+
type: 'array',
|
|
187
|
+
items: {
|
|
188
|
+
type: 'string',
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
required: [
|
|
193
|
+
'defaultNames',
|
|
194
|
+
'globalNames',
|
|
195
|
+
'localNames',
|
|
196
|
+
'effectiveNames',
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
commands: {
|
|
200
|
+
type: 'object',
|
|
201
|
+
properties: {
|
|
202
|
+
globalPath: {
|
|
203
|
+
type: 'string',
|
|
204
|
+
nullable: true,
|
|
205
|
+
},
|
|
206
|
+
globalNames: {
|
|
207
|
+
type: 'array',
|
|
208
|
+
items: {
|
|
209
|
+
type: 'string',
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
localPath: {
|
|
213
|
+
type: 'string',
|
|
214
|
+
nullable: true,
|
|
215
|
+
},
|
|
216
|
+
localNames: {
|
|
217
|
+
type: 'array',
|
|
218
|
+
items: {
|
|
219
|
+
type: 'string',
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
required: ['globalNames', 'localNames'],
|
|
224
|
+
},
|
|
225
|
+
issues: {
|
|
226
|
+
type: 'array',
|
|
227
|
+
items: {
|
|
228
|
+
type: 'string',
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
suggestions: {
|
|
232
|
+
type: 'array',
|
|
233
|
+
items: {
|
|
234
|
+
type: 'string',
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
globalAuthPath: {
|
|
238
|
+
type: 'string',
|
|
239
|
+
nullable: true,
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
required: [
|
|
243
|
+
'providers',
|
|
244
|
+
'defaults',
|
|
245
|
+
'agents',
|
|
246
|
+
'tools',
|
|
247
|
+
'commands',
|
|
248
|
+
'issues',
|
|
249
|
+
'suggestions',
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
'500': {
|
|
256
|
+
description: 'Bad Request',
|
|
257
|
+
content: {
|
|
258
|
+
'application/json': {
|
|
259
|
+
schema: {
|
|
260
|
+
type: 'object',
|
|
261
|
+
properties: {
|
|
262
|
+
error: {
|
|
263
|
+
type: 'string',
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
required: ['error'],
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
async (c) => {
|
|
274
|
+
try {
|
|
275
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
276
|
+
const { cfg, auth } = await readConfig(projectRoot);
|
|
277
|
+
const configuredProviders = getConfiguredProviderIds(cfg, {
|
|
278
|
+
includeDisabled: true,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const providers = await Promise.all(
|
|
282
|
+
configuredProviders.map(async (id) => {
|
|
283
|
+
const ok = await isProviderAuthorized(cfg, id);
|
|
284
|
+
const envVar = getConfiguredProviderEnvVar(cfg, id) ?? null;
|
|
285
|
+
const envConfigured = envVar ? !!process.env[envVar] : false;
|
|
286
|
+
|
|
287
|
+
const globalAuthPath = getSecureAuthPath();
|
|
288
|
+
let hasGlobalAuth = false;
|
|
289
|
+
if (globalAuthPath) {
|
|
290
|
+
const contents =
|
|
291
|
+
await readJsonSafe<Record<string, unknown>>(globalAuthPath);
|
|
292
|
+
hasGlobalAuth = Boolean(contents?.[id]);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const authInfo = auth?.[id];
|
|
296
|
+
const hasStoredSecret = (() => {
|
|
297
|
+
if (!authInfo) return false;
|
|
298
|
+
if (authInfo.type === 'api')
|
|
299
|
+
return Boolean((authInfo as { key?: string }).key);
|
|
300
|
+
if (authInfo.type === 'wallet')
|
|
301
|
+
return Boolean((authInfo as { secret?: string }).secret);
|
|
302
|
+
if (authInfo.type === 'oauth')
|
|
303
|
+
return Boolean(
|
|
304
|
+
(authInfo as { access?: string; refresh?: string }).access ||
|
|
305
|
+
(authInfo as { access?: string; refresh?: string }).refresh,
|
|
306
|
+
);
|
|
307
|
+
return false;
|
|
308
|
+
})();
|
|
309
|
+
|
|
310
|
+
const sources: string[] = [];
|
|
311
|
+
if (envConfigured && envVar) sources.push(`env:${envVar}`);
|
|
312
|
+
if (hasGlobalAuth) sources.push('auth.json');
|
|
313
|
+
|
|
314
|
+
const configured =
|
|
315
|
+
envConfigured ||
|
|
316
|
+
hasGlobalAuth ||
|
|
317
|
+
cfg.defaults.provider === id ||
|
|
318
|
+
hasStoredSecret ||
|
|
319
|
+
Boolean(getConfiguredProviderApiKey(cfg, id));
|
|
70
320
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (!authInfo) return false;
|
|
74
|
-
if (authInfo.type === 'api')
|
|
75
|
-
return Boolean((authInfo as { key?: string }).key);
|
|
76
|
-
if (authInfo.type === 'wallet')
|
|
77
|
-
return Boolean((authInfo as { secret?: string }).secret);
|
|
78
|
-
if (authInfo.type === 'oauth')
|
|
79
|
-
return Boolean(
|
|
80
|
-
(authInfo as { access?: string; refresh?: string }).access ||
|
|
81
|
-
(authInfo as { access?: string; refresh?: string }).refresh,
|
|
82
|
-
);
|
|
83
|
-
return false;
|
|
84
|
-
})();
|
|
85
|
-
|
|
86
|
-
const sources: string[] = [];
|
|
87
|
-
if (envConfigured && envVar) sources.push(`env:${envVar}`);
|
|
88
|
-
if (hasGlobalAuth) sources.push('auth.json');
|
|
89
|
-
|
|
90
|
-
const configured =
|
|
91
|
-
envConfigured ||
|
|
92
|
-
hasGlobalAuth ||
|
|
93
|
-
cfg.defaults.provider === id ||
|
|
94
|
-
hasStoredSecret ||
|
|
95
|
-
Boolean(getConfiguredProviderApiKey(cfg, id));
|
|
96
|
-
|
|
97
|
-
return { id, ok, configured, sources };
|
|
98
|
-
}),
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
const defaults = {
|
|
102
|
-
agent: cfg.defaults.agent,
|
|
103
|
-
provider: cfg.defaults.provider,
|
|
104
|
-
model: cfg.defaults.model,
|
|
105
|
-
providerAuthorized: await isProviderAuthorized(
|
|
106
|
-
cfg,
|
|
107
|
-
cfg.defaults.provider,
|
|
108
|
-
),
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const globalAgentsPath = getGlobalAgentsJsonPath();
|
|
112
|
-
const localAgentsPath = `${projectRoot}/.otto/agents.json`;
|
|
113
|
-
const globalAgents =
|
|
114
|
-
(await readJsonSafe<Record<string, unknown>>(globalAgentsPath)) ?? {};
|
|
115
|
-
const localAgents =
|
|
116
|
-
(await readJsonSafe<Record<string, unknown>>(localAgentsPath)) ?? {};
|
|
117
|
-
|
|
118
|
-
const agents = {
|
|
119
|
-
globalPath: (await fileExists(globalAgentsPath))
|
|
120
|
-
? globalAgentsPath
|
|
121
|
-
: null,
|
|
122
|
-
localPath: (await fileExists(localAgentsPath)) ? localAgentsPath : null,
|
|
123
|
-
globalNames: Object.keys(globalAgents).sort(),
|
|
124
|
-
localNames: Object.keys(localAgents).sort(),
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const defaultToolNames = Array.from(
|
|
128
|
-
new Set([
|
|
129
|
-
...buildFsTools(projectRoot).map((t) => t.name),
|
|
130
|
-
...buildGitTools(projectRoot).map((t) => t.name),
|
|
131
|
-
'finish',
|
|
132
|
-
]),
|
|
133
|
-
).sort();
|
|
134
|
-
|
|
135
|
-
const globalToolsDir = getGlobalToolsDir();
|
|
136
|
-
const localToolsDir = `${projectRoot}/.otto/tools`;
|
|
137
|
-
const globalToolNames = await listDir(globalToolsDir);
|
|
138
|
-
const localToolNames = await listDir(localToolsDir);
|
|
139
|
-
|
|
140
|
-
const tools = {
|
|
141
|
-
defaultNames: defaultToolNames,
|
|
142
|
-
globalPath: globalToolNames.length ? globalToolsDir : null,
|
|
143
|
-
globalNames: globalToolNames.sort(),
|
|
144
|
-
localPath: localToolNames.length ? localToolsDir : null,
|
|
145
|
-
localNames: localToolNames.sort(),
|
|
146
|
-
effectiveNames: Array.from(
|
|
147
|
-
new Set([...defaultToolNames, ...globalToolNames, ...localToolNames]),
|
|
148
|
-
).sort(),
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const globalCommandsDir = getGlobalCommandsDir();
|
|
152
|
-
const localCommandsDir = `${projectRoot}/.otto/commands`;
|
|
153
|
-
const globalCommandFiles = await listDir(globalCommandsDir);
|
|
154
|
-
const localCommandFiles = await listDir(localCommandsDir);
|
|
155
|
-
|
|
156
|
-
const commands = {
|
|
157
|
-
globalPath: globalCommandFiles.length ? globalCommandsDir : null,
|
|
158
|
-
globalNames: globalCommandFiles
|
|
159
|
-
.filter((f) => f.endsWith('.json'))
|
|
160
|
-
.map((f) => f.replace(/\.json$/, ''))
|
|
161
|
-
.sort(),
|
|
162
|
-
localPath: localCommandFiles.length ? localCommandsDir : null,
|
|
163
|
-
localNames: localCommandFiles
|
|
164
|
-
.filter((f) => f.endsWith('.json'))
|
|
165
|
-
.map((f) => f.replace(/\.json$/, ''))
|
|
166
|
-
.sort(),
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const issues: string[] = [];
|
|
170
|
-
if (!defaults.providerAuthorized) {
|
|
171
|
-
issues.push(
|
|
172
|
-
`Default provider '${defaults.provider}' is not authorized`,
|
|
321
|
+
return { id, ok, configured, sources };
|
|
322
|
+
}),
|
|
173
323
|
);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
324
|
+
|
|
325
|
+
const defaults = {
|
|
326
|
+
agent: cfg.defaults.agent,
|
|
327
|
+
provider: cfg.defaults.provider,
|
|
328
|
+
model: cfg.defaults.model,
|
|
329
|
+
providerAuthorized: await isProviderAuthorized(
|
|
330
|
+
cfg,
|
|
331
|
+
cfg.defaults.provider,
|
|
332
|
+
),
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const globalAgentsPath = getGlobalAgentsJsonPath();
|
|
336
|
+
const localAgentsPath = `${projectRoot}/.otto/agents.json`;
|
|
337
|
+
const globalAgents =
|
|
338
|
+
(await readJsonSafe<Record<string, unknown>>(globalAgentsPath)) ?? {};
|
|
339
|
+
const localAgents =
|
|
340
|
+
(await readJsonSafe<Record<string, unknown>>(localAgentsPath)) ?? {};
|
|
341
|
+
|
|
342
|
+
const agents = {
|
|
343
|
+
globalPath: (await fileExists(globalAgentsPath))
|
|
344
|
+
? globalAgentsPath
|
|
345
|
+
: null,
|
|
346
|
+
localPath: (await fileExists(localAgentsPath))
|
|
347
|
+
? localAgentsPath
|
|
348
|
+
: null,
|
|
349
|
+
globalNames: Object.keys(globalAgents).sort(),
|
|
350
|
+
localNames: Object.keys(localAgents).sort(),
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const defaultToolNames = Array.from(
|
|
354
|
+
new Set([
|
|
355
|
+
...buildFsTools(projectRoot).map((t) => t.name),
|
|
356
|
+
...buildGitTools(projectRoot).map((t) => t.name),
|
|
357
|
+
'finish',
|
|
358
|
+
]),
|
|
359
|
+
).sort();
|
|
360
|
+
|
|
361
|
+
const globalToolsDir = getGlobalToolsDir();
|
|
362
|
+
const localToolsDir = `${projectRoot}/.otto/tools`;
|
|
363
|
+
const globalToolNames = await listDir(globalToolsDir);
|
|
364
|
+
const localToolNames = await listDir(localToolsDir);
|
|
365
|
+
|
|
366
|
+
const tools = {
|
|
367
|
+
defaultNames: defaultToolNames,
|
|
368
|
+
globalPath: globalToolNames.length ? globalToolsDir : null,
|
|
369
|
+
globalNames: globalToolNames.sort(),
|
|
370
|
+
localPath: localToolNames.length ? localToolsDir : null,
|
|
371
|
+
localNames: localToolNames.sort(),
|
|
372
|
+
effectiveNames: Array.from(
|
|
373
|
+
new Set([
|
|
374
|
+
...defaultToolNames,
|
|
375
|
+
...globalToolNames,
|
|
376
|
+
...localToolNames,
|
|
377
|
+
]),
|
|
378
|
+
).sort(),
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const globalCommandsDir = getGlobalCommandsDir();
|
|
382
|
+
const localCommandsDir = `${projectRoot}/.otto/commands`;
|
|
383
|
+
const globalCommandFiles = await listDir(globalCommandsDir);
|
|
384
|
+
const localCommandFiles = await listDir(localCommandsDir);
|
|
385
|
+
|
|
386
|
+
const commands = {
|
|
387
|
+
globalPath: globalCommandFiles.length ? globalCommandsDir : null,
|
|
388
|
+
globalNames: globalCommandFiles
|
|
389
|
+
.filter((f) => f.endsWith('.json'))
|
|
390
|
+
.map((f) => f.replace(/\.json$/, ''))
|
|
391
|
+
.sort(),
|
|
392
|
+
localPath: localCommandFiles.length ? localCommandsDir : null,
|
|
393
|
+
localNames: localCommandFiles
|
|
394
|
+
.filter((f) => f.endsWith('.json'))
|
|
395
|
+
.map((f) => f.replace(/\.json$/, ''))
|
|
396
|
+
.sort(),
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
const issues: string[] = [];
|
|
400
|
+
if (!defaults.providerAuthorized) {
|
|
401
|
+
issues.push(
|
|
402
|
+
`Default provider '${defaults.provider}' is not authorized`,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
for (const [scope, entries] of [
|
|
406
|
+
['global', globalAgents],
|
|
407
|
+
['local', localAgents],
|
|
408
|
+
] as const) {
|
|
409
|
+
for (const [name, entry] of Object.entries(entries)) {
|
|
410
|
+
if (
|
|
411
|
+
entry &&
|
|
412
|
+
typeof entry === 'object' &&
|
|
413
|
+
Object.hasOwn(entry, 'tools') &&
|
|
414
|
+
!Array.isArray((entry as { tools?: unknown }).tools)
|
|
415
|
+
) {
|
|
416
|
+
issues.push(`${scope}:${name} tools field must be an array`);
|
|
417
|
+
}
|
|
187
418
|
}
|
|
188
419
|
}
|
|
189
|
-
}
|
|
190
420
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
421
|
+
const suggestions: string[] = [];
|
|
422
|
+
if (!defaults.providerAuthorized) {
|
|
423
|
+
suggestions.push(
|
|
424
|
+
`Run: otto auth login ${defaults.provider} — or switch defaults with: otto models`,
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
if (issues.length) {
|
|
428
|
+
suggestions.push('Review agents.json fields.');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return c.json({
|
|
432
|
+
providers,
|
|
433
|
+
defaults,
|
|
434
|
+
agents,
|
|
435
|
+
tools,
|
|
436
|
+
commands,
|
|
437
|
+
issues,
|
|
438
|
+
suggestions,
|
|
439
|
+
globalAuthPath: getSecureAuthPath(),
|
|
440
|
+
});
|
|
441
|
+
} catch (error) {
|
|
442
|
+
logger.error('Failed to run doctor', error);
|
|
443
|
+
const errorResponse = serializeError(error);
|
|
444
|
+
return c.json(
|
|
445
|
+
errorResponse,
|
|
446
|
+
(errorResponse.error.status || 500) as 500,
|
|
195
447
|
);
|
|
196
448
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return c.json({
|
|
202
|
-
providers,
|
|
203
|
-
defaults,
|
|
204
|
-
agents,
|
|
205
|
-
tools,
|
|
206
|
-
commands,
|
|
207
|
-
issues,
|
|
208
|
-
suggestions,
|
|
209
|
-
globalAuthPath: getSecureAuthPath(),
|
|
210
|
-
});
|
|
211
|
-
} catch (error) {
|
|
212
|
-
logger.error('Failed to run doctor', error);
|
|
213
|
-
const errorResponse = serializeError(error);
|
|
214
|
-
return c.json(errorResponse, (errorResponse.error.status || 500) as 500);
|
|
215
|
-
}
|
|
216
|
-
});
|
|
449
|
+
},
|
|
450
|
+
);
|
|
217
451
|
}
|