octocode-cli 1.5.1 → 1.5.3
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/README.md +28 -9
- package/out/chunks/chunk-375ZNU3A.js +317 -0
- package/out/chunks/{chunk-V64AXUWB.js → chunk-AYILFPX4.js} +1 -1
- package/out/chunks/chunk-BRHVQB64.js +10 -0
- package/out/chunks/{command-help-specs-JZXVSLZ5.js → chunk-KWJUBVXJ.js} +1 -1
- package/out/chunks/chunk-OMXWZJBJ.js +100 -0
- package/out/chunks/command-help-specs-RVJK6V6M.js +8 -0
- package/out/chunks/commands-JFDCC4Q7.js +8 -0
- package/out/chunks/{help-P7TCOYAJ.js → help-H3WGUJO7.js} +1 -1
- package/out/chunks/main-help-BUTDD4IR.js +10 -0
- package/out/chunks/{spinner-URV2OX6O.js → spinner-DXD4JQIU.js} +1 -1
- package/out/chunks/tool-command-L6PVHUNS.js +8 -0
- package/out/octocode-cli.js +1 -1
- package/package.json +2 -2
- package/skills/octocode-brainstorming/scripts/tavily-search.mjs +1 -10
- package/skills/octocode-chrome-devtools/scripts/cdp-runner.mjs +0 -2
- package/skills/octocode-chrome-devtools/scripts/cdp-sandbox.mjs +1 -5
- package/skills/octocode-chrome-devtools/scripts/cdp-template.mjs +1 -10
- package/skills/octocode-chrome-devtools/scripts/open-browser.mjs +0 -3
- package/skills/octocode-chrome-devtools/scripts/sourcemap-resolver.mjs +1 -4
- package/skills/octocode-chrome-devtools/scripts/undercover.mjs +0 -2
- package/skills/octocode-engineer/SKILL.md +7 -33
- package/skills/octocode-engineer/references/cli-reference.md +5 -11
- package/skills/octocode-engineer/references/externals.md +0 -4
- package/skills/octocode-engineer/references/quality-indicators.md +10 -50
- package/skills/octocode-engineer/references/tool-workflows.md +10 -13
- package/skills/octocode-engineer/src/analysis/dependency-summary.test.ts +2 -2
- package/skills/octocode-engineer/src/ast/metrics.test.ts +2 -2
- package/skills/octocode-engineer/src/ast/search-main.ts +2 -15
- package/skills/octocode-engineer/src/ast/search.ts +1 -7
- package/skills/octocode-engineer/src/ast/tree-sitter.test.ts +4 -4
- package/skills/octocode-engineer/src/ast/tree-sitter.ts +2 -2
- package/skills/octocode-engineer/src/collectors/chains.ts +3 -10
- package/skills/octocode-engineer/src/collectors/prototype-pollution.ts +3 -3
- package/skills/octocode-engineer/src/collectors/security.ts +3 -3
- package/skills/octocode-engineer/src/common/ensure-deps.ts +3 -20
- package/skills/octocode-engineer/src/detectors/code-quality.ts +0 -1
- package/skills/octocode-engineer/src/index.test.ts +1 -1
- package/skills/octocode-engineer/src/index.ts +1 -11
- package/skills/octocode-engineer/src/pipeline/affected.ts +2 -7
- package/skills/octocode-engineer/src/pipeline/baseline.ts +2 -8
- package/skills/octocode-engineer/src/pipeline/cache.ts +2 -2
- package/skills/octocode-engineer/src/pipeline/config-loader.test.ts +1 -2
- package/skills/octocode-engineer/src/pipeline/config-loader.ts +0 -9
- package/skills/octocode-engineer/src/pipeline/create-options.ts +2 -15
- package/skills/octocode-engineer/src/pipeline/reporters.ts +2 -12
- package/skills/octocode-engineer/src/reporting/summary-md.test.ts +2 -2
- package/skills/octocode-engineer/src/run.ts +1 -7
- package/skills/octocode-engineer/src/types/constants.ts +0 -5
- package/skills/octocode-engineer/src/types/core.ts +0 -5
- package/skills/octocode-news/eslint.config.mjs +1 -6
- package/skills/octocode-news/src/rss-core.ts +2 -4
- package/skills/octocode-research/ecosystem.config.cjs +0 -31
- package/skills/octocode-research/package.json +1 -1
- package/skills/octocode-research/scripts/server.js +272 -230
- package/skills/octocode-research/src/__tests__/integration/circuitBreaker.test.ts +0 -24
- package/skills/octocode-research/src/__tests__/integration/routes.test.ts +0 -10
- package/skills/octocode-research/src/__tests__/integration/serverLifecycle.test.ts +5 -22
- package/skills/octocode-research/src/__tests__/integration/toolsRoutes.test.ts +0 -27
- package/skills/octocode-research/src/__tests__/unit/circuitBreaker.test.ts +0 -29
- package/skills/octocode-research/src/__tests__/unit/errorHandler.test.ts +1 -12
- package/skills/octocode-research/src/__tests__/unit/httpPreprocess.test.ts +1 -9
- package/skills/octocode-research/src/__tests__/unit/logger.test.ts +0 -7
- package/skills/octocode-research/src/__tests__/unit/queryParser.test.ts +1 -7
- package/skills/octocode-research/src/__tests__/unit/responseBuilder.test.ts +0 -11
- package/skills/octocode-research/src/__tests__/unit/retry.test.ts +1 -18
- package/skills/octocode-research/src/__tests__/unit/schemas.test.ts +2 -25
- package/skills/octocode-research/src/index.ts +0 -27
- package/skills/octocode-research/src/mcpCache.ts +3 -18
- package/skills/octocode-research/src/middleware/errorHandler.ts +1 -4
- package/skills/octocode-research/src/middleware/logger.ts +1 -6
- package/skills/octocode-research/src/middleware/queryParser.ts +3 -22
- package/skills/octocode-research/src/routes/github.ts +0 -11
- package/skills/octocode-research/src/routes/local.ts +0 -12
- package/skills/octocode-research/src/routes/lsp.ts +1 -10
- package/skills/octocode-research/src/routes/package.ts +0 -2
- package/skills/octocode-research/src/routes/prompts.ts +2 -32
- package/skills/octocode-research/src/routes/tools.ts +12 -177
- package/skills/octocode-research/src/server-init.ts +0 -32
- package/skills/octocode-research/src/server.ts +8 -37
- package/skills/octocode-research/src/types/errorGuards.ts +0 -15
- package/skills/octocode-research/src/types/guards.ts +6 -27
- package/skills/octocode-research/src/types/mcp.ts +1 -8
- package/skills/octocode-research/src/types/responses.ts +0 -15
- package/skills/octocode-research/src/utils/asyncTimeout.ts +1 -63
- package/skills/octocode-research/src/utils/circuitBreaker.ts +43 -166
- package/skills/octocode-research/src/utils/colors.ts +3 -17
- package/skills/octocode-research/src/utils/errorQueue.ts +6 -28
- package/skills/octocode-research/src/utils/logger.ts +11 -93
- package/skills/octocode-research/src/utils/resilience.ts +22 -77
- package/skills/octocode-research/src/utils/responseBuilder.ts +26 -78
- package/skills/octocode-research/src/utils/responseFactory.ts +0 -7
- package/skills/octocode-research/src/utils/responseParser.ts +11 -59
- package/skills/octocode-research/src/utils/retry.ts +12 -75
- package/skills/octocode-research/src/utils/routeFactory.ts +10 -61
- package/skills/octocode-research/src/utils/url.ts +0 -5
- package/skills/octocode-research/src/validation/httpPreprocess.ts +15 -61
- package/skills/octocode-research/src/validation/schemas.ts +14 -101
- package/skills/octocode-research/src/validation/toolCallSchema.ts +5 -65
- package/skills/octocode-research/tsdown.config.ts +2 -7
- package/skills/octocode-search-skill/SKILL.md +9 -2
- package/skills/octocode-search-skill/references/discovery-surfaces.md +2 -1
- package/skills/octocode-slides/scripts/navbridge.js +1 -14
- package/skills/octocode-slides/scripts/presenter.js +2 -61
- package/skills/octocode-stats/scripts/build_dashboard.mjs +1 -19
- package/out/chunks/chunk-7476PETK.js +0 -309
- package/out/chunks/chunk-OQBJTZWK.js +0 -60
- package/out/chunks/chunk-UCZCF3BQ.js +0 -9
- package/out/chunks/commands-XBFPLHSQ.js +0 -8
- package/out/chunks/main-help-ULF5PAQY.js +0 -10
- package/out/chunks/tool-command-M6VA7P2F.js +0 -8
- package/skills/octocode-research/AGENTS.md +0 -349
|
@@ -1,23 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tools Info Routes - Expose tool metadata via HTTP
|
|
3
|
-
*
|
|
4
|
-
* Routes:
|
|
5
|
-
* GET /tools/list - List all tools (concise)
|
|
6
|
-
* GET /tools/info - List all tools with details
|
|
7
|
-
* GET /tools/info/:toolName - Get specific tool info
|
|
8
|
-
* POST /tools/call/:toolName - Execute a tool directly (simplified!)
|
|
9
|
-
*
|
|
10
|
-
* Query params:
|
|
11
|
-
* schema=true - Include schema information
|
|
12
|
-
* hints=true - Include hints information
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
1
|
import { Router, type Request, type Response, type NextFunction } from 'express';
|
|
16
2
|
import { getMcpContent } from '../mcpCache.js';
|
|
17
3
|
import { transformToJsonSchema } from '../types/mcp.js';
|
|
18
|
-
import { z } from 'zod
|
|
4
|
+
import { z } from 'zod';
|
|
19
5
|
|
|
20
|
-
// Import Zod schemas from octocode-mcp (source of truth)
|
|
21
6
|
import {
|
|
22
7
|
GitHubCodeSearchQuerySchema,
|
|
23
8
|
GitHubViewRepoStructureQuerySchema,
|
|
@@ -62,7 +47,6 @@ import { checkReadiness } from '../middleware/readiness.js';
|
|
|
62
47
|
|
|
63
48
|
export const toolsRoutes = Router();
|
|
64
49
|
|
|
65
|
-
// Apply readiness check middleware to all tools routes
|
|
66
50
|
toolsRoutes.use(checkReadiness);
|
|
67
51
|
|
|
68
52
|
declare const __PACKAGE_VERSION__: string;
|
|
@@ -73,27 +57,20 @@ interface ToolsInfoQuery {
|
|
|
73
57
|
hints?: string;
|
|
74
58
|
}
|
|
75
59
|
|
|
76
|
-
// ============================================================================
|
|
77
|
-
// Zod Schema Registry - Maps tool names to their Zod schemas (source of truth)
|
|
78
|
-
// ============================================================================
|
|
79
60
|
|
|
80
61
|
const TOOL_ZOD_SCHEMAS: Record<string, z.ZodType> = {
|
|
81
|
-
// GitHub tools
|
|
82
62
|
githubSearchCode: GitHubCodeSearchQuerySchema,
|
|
83
63
|
githubGetFileContent: FileContentQuerySchema,
|
|
84
64
|
githubViewRepoStructure: GitHubViewRepoStructureQuerySchema,
|
|
85
65
|
githubSearchRepositories: GitHubReposSearchSingleQuerySchema,
|
|
86
66
|
githubSearchPullRequests: GitHubPullRequestSearchQuerySchema,
|
|
87
|
-
// Local tools
|
|
88
67
|
localSearchCode: RipgrepQuerySchema,
|
|
89
68
|
localGetFileContent: FetchContentQuerySchema,
|
|
90
69
|
localFindFiles: FindFilesQuerySchema,
|
|
91
70
|
localViewStructure: ViewStructureQuerySchema,
|
|
92
|
-
// LSP tools
|
|
93
71
|
lspGotoDefinition: LSPGotoDefinitionQuerySchema,
|
|
94
72
|
lspFindReferences: LSPFindReferencesQuerySchema,
|
|
95
73
|
lspCallHierarchy: LSPCallHierarchyQuerySchema,
|
|
96
|
-
// Package tools
|
|
97
74
|
packageSearch: PackageSearchQuerySchema,
|
|
98
75
|
};
|
|
99
76
|
|
|
@@ -110,12 +87,7 @@ function getToolJsonSchema(toolName: string): Record<string, unknown> | null {
|
|
|
110
87
|
return zodSchema ? toJsonSchema(zodSchema) : null;
|
|
111
88
|
}
|
|
112
89
|
|
|
113
|
-
|
|
114
|
-
* GET /tools/list - Static list of all tools (concise discovery)
|
|
115
|
-
*
|
|
116
|
-
* Returns static JSON with tool names and short descriptions.
|
|
117
|
-
* Use /tools/info/:toolName to get full description + schema BEFORE calling a tool.
|
|
118
|
-
*/
|
|
90
|
+
|
|
119
91
|
toolsRoutes.get('/list', (_req: Request, res: Response) => {
|
|
120
92
|
res.json({
|
|
121
93
|
success: true,
|
|
@@ -140,9 +112,7 @@ toolsRoutes.get('/list', (_req: Request, res: Response) => {
|
|
|
140
112
|
});
|
|
141
113
|
});
|
|
142
114
|
|
|
143
|
-
|
|
144
|
-
* GET /tools/info - Get info about all available tools
|
|
145
|
-
*/
|
|
115
|
+
|
|
146
116
|
toolsRoutes.get('/info', async (
|
|
147
117
|
req: Request,
|
|
148
118
|
res: Response,
|
|
@@ -198,21 +168,6 @@ toolsRoutes.get('/info', async (
|
|
|
198
168
|
}
|
|
199
169
|
});
|
|
200
170
|
|
|
201
|
-
/**
|
|
202
|
-
* GET /tools/info/:toolName - Get full info for a specific tool (call before using!)
|
|
203
|
-
*
|
|
204
|
-
* Returns complete description, JSON schema, and hints for a tool.
|
|
205
|
-
* ALWAYS call this before using a tool to understand its parameters.
|
|
206
|
-
*
|
|
207
|
-
* @example
|
|
208
|
-
* GET /tools/info/localSearchCode
|
|
209
|
-
*
|
|
210
|
-
* Response includes:
|
|
211
|
-
* - name: Tool name
|
|
212
|
-
* - description: Full description with usage details
|
|
213
|
-
* - schema: Complete JSON schema with all parameters
|
|
214
|
-
* - hints: Success/empty result hints
|
|
215
|
-
*/
|
|
216
171
|
toolsRoutes.get('/info/:toolName', async (
|
|
217
172
|
req: Request,
|
|
218
173
|
res: Response,
|
|
@@ -223,8 +178,8 @@ toolsRoutes.get('/info/:toolName', async (
|
|
|
223
178
|
|
|
224
179
|
const { toolName } = req.params;
|
|
225
180
|
const query = req.query as ToolsInfoQuery;
|
|
226
|
-
const includeSchema = query.schema !== 'false';
|
|
227
|
-
const includeHints = query.hints !== 'false';
|
|
181
|
+
const includeSchema = query.schema !== 'false';
|
|
182
|
+
const includeHints = query.hints !== 'false';
|
|
228
183
|
|
|
229
184
|
const tool = content.tools[toolName];
|
|
230
185
|
|
|
@@ -248,15 +203,13 @@ toolsRoutes.get('/info/:toolName', async (
|
|
|
248
203
|
};
|
|
249
204
|
|
|
250
205
|
if (includeSchema) {
|
|
251
|
-
// Try to get the actual Zod schema (source of truth with correct types and required fields)
|
|
252
206
|
const zodJsonSchema = getToolJsonSchema(toolName);
|
|
253
207
|
if (zodJsonSchema) {
|
|
254
208
|
result.inputSchema = zodJsonSchema;
|
|
255
|
-
result._schemaSource = 'zod';
|
|
209
|
+
result._schemaSource = 'zod';
|
|
256
210
|
} else {
|
|
257
|
-
// Fallback to metadata-based schema (less accurate)
|
|
258
211
|
result.inputSchema = transformToJsonSchema(tool.schema, tool.name);
|
|
259
|
-
result._schemaSource = 'metadata';
|
|
212
|
+
result._schemaSource = 'metadata';
|
|
260
213
|
}
|
|
261
214
|
}
|
|
262
215
|
|
|
@@ -277,9 +230,7 @@ toolsRoutes.get('/info/:toolName', async (
|
|
|
277
230
|
}
|
|
278
231
|
});
|
|
279
232
|
|
|
280
|
-
|
|
281
|
-
* GET /tools/metadata - Get raw complete metadata (advanced)
|
|
282
|
-
*/
|
|
233
|
+
|
|
283
234
|
toolsRoutes.get('/metadata', async (
|
|
284
235
|
_req: Request,
|
|
285
236
|
res: Response,
|
|
@@ -303,28 +254,6 @@ toolsRoutes.get('/metadata', async (
|
|
|
303
254
|
}
|
|
304
255
|
});
|
|
305
256
|
|
|
306
|
-
/**
|
|
307
|
-
* GET /tools/schemas - Get all tools with their complete JSON schemas
|
|
308
|
-
*
|
|
309
|
-
* Returns all tool names with their full JSON schemas (from Zod).
|
|
310
|
-
* Useful for bulk schema retrieval without calling /info/:toolName for each.
|
|
311
|
-
*
|
|
312
|
-
* @example
|
|
313
|
-
* GET /tools/schemas
|
|
314
|
-
*
|
|
315
|
-
* Response:
|
|
316
|
-
* {
|
|
317
|
-
* "success": true,
|
|
318
|
-
* "data": {
|
|
319
|
-
* "totalTools": 13,
|
|
320
|
-
* "schemas": {
|
|
321
|
-
* "localSearchCode": { "type": "object", "properties": {...} },
|
|
322
|
-
* "githubSearchCode": { "type": "object", "properties": {...} },
|
|
323
|
-
* ...
|
|
324
|
-
* }
|
|
325
|
-
* }
|
|
326
|
-
* }
|
|
327
|
-
*/
|
|
328
257
|
toolsRoutes.get('/schemas', async (
|
|
329
258
|
_req: Request,
|
|
330
259
|
res: Response,
|
|
@@ -354,21 +283,6 @@ toolsRoutes.get('/schemas', async (
|
|
|
354
283
|
}
|
|
355
284
|
});
|
|
356
285
|
|
|
357
|
-
/**
|
|
358
|
-
* GET /tools/system - Get the FULL system instructions
|
|
359
|
-
*
|
|
360
|
-
* Returns the complete system prompt that should be loaded into context FIRST.
|
|
361
|
-
* This defines the agent's behavior, methodology, and best practices.
|
|
362
|
-
*
|
|
363
|
-
* @example
|
|
364
|
-
* GET /tools/system
|
|
365
|
-
*
|
|
366
|
-
* Response:
|
|
367
|
-
* {
|
|
368
|
-
* "instructions": "## Expert Code Forensics Agent...",
|
|
369
|
-
* "_meta": { "charCount": 5432, "version": "2.2.0" }
|
|
370
|
-
* }
|
|
371
|
-
*/
|
|
372
286
|
toolsRoutes.get('/system', async (
|
|
373
287
|
_req: Request,
|
|
374
288
|
res: Response,
|
|
@@ -391,25 +305,6 @@ toolsRoutes.get('/system', async (
|
|
|
391
305
|
}
|
|
392
306
|
});
|
|
393
307
|
|
|
394
|
-
/**
|
|
395
|
-
* GET /tools/initContext - Combined system prompt and all tool schemas
|
|
396
|
-
*
|
|
397
|
-
* Combines /tools/system + /tools/schemas in one call for faster init.
|
|
398
|
-
* Returns:
|
|
399
|
-
* - system_prompt: Full instructions
|
|
400
|
-
* - tools_schema: All tool JSON schemas from Zod
|
|
401
|
-
*
|
|
402
|
-
* @example
|
|
403
|
-
* GET /tools/initContext
|
|
404
|
-
*
|
|
405
|
-
* Response:
|
|
406
|
-
* {
|
|
407
|
-
* "success": true,
|
|
408
|
-
* "system_prompt": "## Expert Code Forensics Agent...",
|
|
409
|
-
* "tools_schema": { "localSearchCode": {...}, ... },
|
|
410
|
-
* "_meta": { "promptCharCount": 5432, "toolsCount": 13, "version": "2.2.0" }
|
|
411
|
-
* }
|
|
412
|
-
*/
|
|
413
308
|
toolsRoutes.get('/initContext', async (
|
|
414
309
|
_req: Request,
|
|
415
310
|
res: Response,
|
|
@@ -418,7 +313,6 @@ toolsRoutes.get('/initContext', async (
|
|
|
418
313
|
try {
|
|
419
314
|
const content = getMcpContent();
|
|
420
315
|
|
|
421
|
-
// Build schemas (same as /tools/schemas)
|
|
422
316
|
const schemas: Record<string, Record<string, unknown>> = {};
|
|
423
317
|
for (const toolName of Object.keys(TOOL_ZOD_SCHEMAS)) {
|
|
424
318
|
const schema = getToolJsonSchema(toolName);
|
|
@@ -440,16 +334,10 @@ toolsRoutes.get('/initContext', async (
|
|
|
440
334
|
}
|
|
441
335
|
});
|
|
442
336
|
|
|
443
|
-
// ============================================================================
|
|
444
|
-
// Tool Registry - Maps tool names to functions and resilience wrappers
|
|
445
|
-
// ============================================================================
|
|
446
337
|
|
|
447
338
|
type ResilienceFn = <T>(fn: () => Promise<T>, toolName: string) => Promise<T>;
|
|
448
339
|
|
|
449
|
-
|
|
450
|
-
* Tool registry entry for dynamic dispatch.
|
|
451
|
-
* Tool functions are dispatched by name at runtime; Zod schemas validate inputs.
|
|
452
|
-
*/
|
|
340
|
+
|
|
453
341
|
interface ToolEntry {
|
|
454
342
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
455
343
|
fn: (params: any) => Promise<any>;
|
|
@@ -458,54 +346,43 @@ interface ToolEntry {
|
|
|
458
346
|
}
|
|
459
347
|
|
|
460
348
|
const TOOL_REGISTRY: Record<string, ToolEntry> = {
|
|
461
|
-
// GitHub tools
|
|
462
349
|
githubSearchCode: { fn: githubSearchCode, resilience: withGitHubResilience, category: 'github' },
|
|
463
350
|
githubGetFileContent: { fn: githubGetFileContent, resilience: withGitHubResilience, category: 'github' },
|
|
464
351
|
githubViewRepoStructure: { fn: githubViewRepoStructure, resilience: withGitHubResilience, category: 'github' },
|
|
465
352
|
githubSearchRepositories: { fn: githubSearchRepositories, resilience: withGitHubResilience, category: 'github' },
|
|
466
353
|
githubSearchPullRequests: { fn: githubSearchPullRequests, resilience: withGitHubResilience, category: 'github' },
|
|
467
354
|
|
|
468
|
-
// Local tools
|
|
469
355
|
localSearchCode: { fn: localSearchCode, resilience: withLocalResilience, category: 'local' },
|
|
470
356
|
localGetFileContent: { fn: localGetFileContent, resilience: withLocalResilience, category: 'local' },
|
|
471
357
|
localFindFiles: { fn: localFindFiles, resilience: withLocalResilience, category: 'local' },
|
|
472
358
|
localViewStructure: { fn: localViewStructure, resilience: withLocalResilience, category: 'local' },
|
|
473
359
|
|
|
474
|
-
// LSP tools
|
|
475
360
|
lspGotoDefinition: { fn: lspGotoDefinition, resilience: withLspResilience, category: 'lsp' },
|
|
476
361
|
lspFindReferences: { fn: lspFindReferences, resilience: withLspResilience, category: 'lsp' },
|
|
477
362
|
lspCallHierarchy: { fn: lspCallHierarchy, resilience: withLspResilience, category: 'lsp' },
|
|
478
363
|
|
|
479
|
-
// Package tools
|
|
480
364
|
packageSearch: { fn: packageSearch, resilience: withPackageResilience, category: 'package' },
|
|
481
365
|
};
|
|
482
366
|
|
|
483
|
-
|
|
484
|
-
* Extract repository identifiers from query parameters for session logging
|
|
485
|
-
*/
|
|
367
|
+
|
|
486
368
|
function extractReposFromQueries(queries: unknown[]): string[] {
|
|
487
369
|
const repos: string[] = [];
|
|
488
370
|
for (const query of queries) {
|
|
489
371
|
const q = query as Record<string, unknown>;
|
|
490
|
-
// GitHub repos: owner/repo format
|
|
491
372
|
if (q.owner && q.repo) {
|
|
492
373
|
repos.push(`${q.owner}/${q.repo}`);
|
|
493
374
|
}
|
|
494
|
-
// Local paths
|
|
495
375
|
if (q.path && typeof q.path === 'string') {
|
|
496
376
|
repos.push(q.path);
|
|
497
377
|
}
|
|
498
|
-
// LSP uris
|
|
499
378
|
if (q.uri && typeof q.uri === 'string') {
|
|
500
379
|
repos.push(q.uri);
|
|
501
380
|
}
|
|
502
381
|
}
|
|
503
|
-
return [...new Set(repos)];
|
|
382
|
+
return [...new Set(repos)];
|
|
504
383
|
}
|
|
505
384
|
|
|
506
|
-
|
|
507
|
-
* Extract research parameters from the first query for logging
|
|
508
|
-
*/
|
|
385
|
+
|
|
509
386
|
function extractResearchParams(queries: unknown[]): {
|
|
510
387
|
mainResearchGoal?: string;
|
|
511
388
|
researchGoal?: string;
|
|
@@ -520,48 +397,15 @@ function extractResearchParams(queries: unknown[]): {
|
|
|
520
397
|
};
|
|
521
398
|
}
|
|
522
399
|
|
|
523
|
-
/**
|
|
524
|
-
* POST /tools/call/:toolName - Execute a tool directly with JSON body
|
|
525
|
-
*
|
|
526
|
-
* This is the SIMPLIFIED way to call tools. Instead of URL-encoded query params,
|
|
527
|
-
* just POST a JSON body with your queries array.
|
|
528
|
-
*
|
|
529
|
-
* @example
|
|
530
|
-
* POST /tools/call/localSearchCode
|
|
531
|
-
* Content-Type: application/json
|
|
532
|
-
*
|
|
533
|
-
* {
|
|
534
|
-
* "queries": [{
|
|
535
|
-
* "mainResearchGoal": "Find authentication handlers",
|
|
536
|
-
* "researchGoal": "Locate auth middleware",
|
|
537
|
-
* "reasoning": "Understanding auth flow",
|
|
538
|
-
* "pattern": "authenticate",
|
|
539
|
-
* "path": "/Users/me/project",
|
|
540
|
-
* "type": "ts"
|
|
541
|
-
* }]
|
|
542
|
-
* }
|
|
543
|
-
*
|
|
544
|
-
* Response:
|
|
545
|
-
* {
|
|
546
|
-
* "tool": "localSearchCode",
|
|
547
|
-
* "success": true,
|
|
548
|
-
* "data": { ... parsed tool response ... },
|
|
549
|
-
* "hints": ["Use lineHint for LSP tools", ...]
|
|
550
|
-
* }
|
|
551
|
-
*/
|
|
552
400
|
toolsRoutes.post('/call/:toolName', async (
|
|
553
401
|
req: Request,
|
|
554
402
|
res: Response,
|
|
555
403
|
next: NextFunction
|
|
556
404
|
) => {
|
|
557
|
-
// Set up AbortController for client disconnection handling
|
|
558
|
-
// NOTE: req.on('close') fires when request body is consumed, not client disconnect
|
|
559
|
-
// Use res.on('close') + socket.destroyed to detect actual client disconnection
|
|
560
405
|
const abortController = new AbortController();
|
|
561
406
|
let isAborted = false;
|
|
562
407
|
|
|
563
408
|
res.on('close', () => {
|
|
564
|
-
// Only abort if client disconnected before we finished responding
|
|
565
409
|
if (!res.writableEnded && req.socket?.destroyed) {
|
|
566
410
|
isAborted = true;
|
|
567
411
|
abortController.abort();
|
|
@@ -571,7 +415,6 @@ toolsRoutes.post('/call/:toolName', async (
|
|
|
571
415
|
try {
|
|
572
416
|
const { toolName } = req.params;
|
|
573
417
|
|
|
574
|
-
// Validate tool exists
|
|
575
418
|
const toolEntry = TOOL_REGISTRY[toolName];
|
|
576
419
|
if (!toolEntry) {
|
|
577
420
|
const availableTools = Object.keys(TOOL_REGISTRY);
|
|
@@ -588,7 +431,6 @@ toolsRoutes.post('/call/:toolName', async (
|
|
|
588
431
|
return;
|
|
589
432
|
}
|
|
590
433
|
|
|
591
|
-
// Validate request body using schema
|
|
592
434
|
const validation = validateToolCallBody(req.body);
|
|
593
435
|
if (!validation.success) {
|
|
594
436
|
res.status(400).json({
|
|
@@ -602,19 +444,15 @@ toolsRoutes.post('/call/:toolName', async (
|
|
|
602
444
|
|
|
603
445
|
const { queries } = validation.data!;
|
|
604
446
|
|
|
605
|
-
// Check if request was aborted before execution
|
|
606
447
|
if (isAborted) return;
|
|
607
448
|
|
|
608
|
-
// Execute tool with resilience (includes timeout + circuit breaker + retry)
|
|
609
449
|
const rawResult = await toolEntry.resilience(
|
|
610
450
|
() => toolEntry.fn({ queries }),
|
|
611
451
|
toolName
|
|
612
452
|
);
|
|
613
453
|
|
|
614
|
-
// Check if request was aborted after execution
|
|
615
454
|
if (isAborted) return;
|
|
616
455
|
|
|
617
|
-
// Log tool call for session telemetry
|
|
618
456
|
const repos = extractReposFromQueries(queries);
|
|
619
457
|
const researchParams = extractResearchParams(queries);
|
|
620
458
|
fireAndForgetWithTimeout(
|
|
@@ -631,7 +469,6 @@ toolsRoutes.post('/call/:toolName', async (
|
|
|
631
469
|
|
|
632
470
|
const mcpResponse = rawResult as { content: Array<{ type: string; text: string }> };
|
|
633
471
|
|
|
634
|
-
// For multiple queries, return bulk response format
|
|
635
472
|
if (queries.length > 1) {
|
|
636
473
|
const bulkParsed = parseToolResponseBulk(mcpResponse);
|
|
637
474
|
|
|
@@ -647,7 +484,6 @@ toolsRoutes.post('/call/:toolName', async (
|
|
|
647
484
|
return;
|
|
648
485
|
}
|
|
649
486
|
|
|
650
|
-
// Single query - use existing response format for backward compatibility
|
|
651
487
|
const parsed = parseToolResponse(mcpResponse);
|
|
652
488
|
|
|
653
489
|
res.status(parsed.isError ? 500 : 200).json({
|
|
@@ -658,7 +494,6 @@ toolsRoutes.post('/call/:toolName', async (
|
|
|
658
494
|
research: parsed.research,
|
|
659
495
|
});
|
|
660
496
|
} catch (error) {
|
|
661
|
-
// Skip error handling if request was aborted
|
|
662
497
|
if (isAborted) return;
|
|
663
498
|
next(error);
|
|
664
499
|
}
|
|
@@ -1,24 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Server Init Script — Detached Daemon Launcher
|
|
3
|
-
*
|
|
4
|
-
* 1. Checks if server is already running (health check)
|
|
5
|
-
* 2. If running → prints "ok" and exits
|
|
6
|
-
* 3. If not → starts server as **detached** process, waits for health, prints "ok", exits
|
|
7
|
-
*
|
|
8
|
-
* Lifecycle: The server runs as an independent daemon (detached, unref'd).
|
|
9
|
-
* No client owns the server — it self-terminates after 30 minutes of idle.
|
|
10
|
-
* Every invocation of this script exits after confirming the server is healthy.
|
|
11
|
-
*
|
|
12
|
-
* Usage: npm run server-init
|
|
13
|
-
* Exit codes: 0 = success, 1 = error
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
1
|
import { spawn } from 'child_process';
|
|
17
2
|
import { join } from 'path';
|
|
18
3
|
|
|
19
|
-
// =============================================================================
|
|
20
|
-
// Configuration (Environment Variables)
|
|
21
|
-
// =============================================================================
|
|
22
4
|
|
|
23
5
|
const PORT = parseInt(process.env.OCTOCODE_RESEARCH_PORT || process.env.OCTOCODE_PORT || '1987', 10);
|
|
24
6
|
const HOST = process.env.OCTOCODE_RESEARCH_HOST || 'localhost';
|
|
@@ -30,9 +12,6 @@ interface HealthResponse {
|
|
|
30
12
|
status: 'ok' | 'initializing' | string;
|
|
31
13
|
}
|
|
32
14
|
|
|
33
|
-
// =============================================================================
|
|
34
|
-
// Health Check
|
|
35
|
-
// =============================================================================
|
|
36
15
|
|
|
37
16
|
async function checkHealth(): Promise<HealthResponse | null> {
|
|
38
17
|
try {
|
|
@@ -53,9 +32,6 @@ async function checkHealth(): Promise<HealthResponse | null> {
|
|
|
53
32
|
}
|
|
54
33
|
}
|
|
55
34
|
|
|
56
|
-
// =============================================================================
|
|
57
|
-
// Server Start — detached daemon (survives parent exit)
|
|
58
|
-
// =============================================================================
|
|
59
35
|
|
|
60
36
|
function startServer(): Promise<void> {
|
|
61
37
|
return new Promise((resolve, reject) => {
|
|
@@ -82,9 +58,6 @@ function startServer(): Promise<void> {
|
|
|
82
58
|
});
|
|
83
59
|
}
|
|
84
60
|
|
|
85
|
-
// =============================================================================
|
|
86
|
-
// Wait for Ready (with exponential backoff)
|
|
87
|
-
// =============================================================================
|
|
88
61
|
|
|
89
62
|
async function waitForReady(): Promise<boolean> {
|
|
90
63
|
const startTime = Date.now();
|
|
@@ -108,12 +81,8 @@ async function waitForReady(): Promise<boolean> {
|
|
|
108
81
|
return false;
|
|
109
82
|
}
|
|
110
83
|
|
|
111
|
-
// =============================================================================
|
|
112
|
-
// Main
|
|
113
|
-
// =============================================================================
|
|
114
84
|
|
|
115
85
|
async function main(): Promise<void> {
|
|
116
|
-
// Fast path: server already running → exit immediately
|
|
117
86
|
const initialHealth = await checkHealth();
|
|
118
87
|
|
|
119
88
|
if (initialHealth?.status === 'ok') {
|
|
@@ -133,7 +102,6 @@ async function main(): Promise<void> {
|
|
|
133
102
|
}
|
|
134
103
|
}
|
|
135
104
|
|
|
136
|
-
// Server not running — start it (detached)
|
|
137
105
|
console.log('[server-init] Server not running, starting detached daemon...');
|
|
138
106
|
|
|
139
107
|
try {
|
|
@@ -25,7 +25,6 @@ const getPort = (raw?: string): number => {
|
|
|
25
25
|
|
|
26
26
|
const port = Number(raw);
|
|
27
27
|
|
|
28
|
-
// Strictly allow only the non-privileged, user-registered range
|
|
29
28
|
if (!Number.isInteger(port) || port < 1024 || port > 65535) {
|
|
30
29
|
throw new Error(
|
|
31
30
|
`Invalid OCTOCODE_RESEARCH_PORT: "${raw}". ` +
|
|
@@ -38,15 +37,14 @@ const getPort = (raw?: string): number => {
|
|
|
38
37
|
|
|
39
38
|
const PORT = getPort(process.env.OCTOCODE_RESEARCH_PORT || process.env.OCTOCODE_PORT);
|
|
40
39
|
|
|
41
|
-
const MAX_IDLE_TIME_MS = 30 * 60 * 1000;
|
|
42
|
-
const IDLE_CHECK_INTERVAL_MS = 120 * 1000;
|
|
40
|
+
const MAX_IDLE_TIME_MS = 30 * 60 * 1000;
|
|
41
|
+
const IDLE_CHECK_INTERVAL_MS = 120 * 1000;
|
|
43
42
|
|
|
44
43
|
let server: Server | null = null;
|
|
45
44
|
let lastRequestTime: number = Date.now();
|
|
46
45
|
let idleCheckInterval: NodeJS.Timeout | null = null;
|
|
47
46
|
let isShuttingDown = false;
|
|
48
47
|
|
|
49
|
-
// PID file for daemon lifecycle management
|
|
50
48
|
const OCTOCODE_DIR = process.env.OCTOCODE_HOME || join(homedir(), '.octocode');
|
|
51
49
|
export const PID_FILE = join(OCTOCODE_DIR, `research-server-${PORT}.pid`);
|
|
52
50
|
|
|
@@ -55,7 +53,7 @@ function writePidFile(): void {
|
|
|
55
53
|
mkdirSync(OCTOCODE_DIR, { recursive: true, mode: 0o700 });
|
|
56
54
|
writeFileSync(PID_FILE, String(process.pid), { mode: 0o600 });
|
|
57
55
|
} catch {
|
|
58
|
-
|
|
56
|
+
void 0;
|
|
59
57
|
}
|
|
60
58
|
}
|
|
61
59
|
|
|
@@ -63,13 +61,11 @@ function removePidFile(): void {
|
|
|
63
61
|
try {
|
|
64
62
|
unlinkSync(PID_FILE);
|
|
65
63
|
} catch {
|
|
66
|
-
|
|
64
|
+
void 0;
|
|
67
65
|
}
|
|
68
66
|
}
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
* Check if server has been idle and should restart
|
|
72
|
-
*/
|
|
68
|
+
|
|
73
69
|
function checkIdleRestart(): void {
|
|
74
70
|
const idleTime = Date.now() - lastRequestTime;
|
|
75
71
|
const idleSeconds = Math.floor(idleTime / 1000);
|
|
@@ -82,23 +78,18 @@ function checkIdleRestart(): void {
|
|
|
82
78
|
}
|
|
83
79
|
}
|
|
84
80
|
|
|
85
|
-
|
|
86
|
-
* Start periodic idle checking
|
|
87
|
-
*/
|
|
81
|
+
|
|
88
82
|
function startIdleCheck(): void {
|
|
89
83
|
if (idleCheckInterval) return;
|
|
90
84
|
|
|
91
85
|
idleCheckInterval = setInterval(checkIdleRestart, IDLE_CHECK_INTERVAL_MS);
|
|
92
86
|
|
|
93
|
-
// Unref so it doesn't prevent process exit
|
|
94
87
|
idleCheckInterval.unref();
|
|
95
88
|
|
|
96
89
|
console.log(dimLog(`⏱️ Idle check started (${IDLE_CHECK_INTERVAL_MS / 1000}s interval, ${MAX_IDLE_TIME_MS / 1000}s threshold)`));
|
|
97
90
|
}
|
|
98
91
|
|
|
99
|
-
|
|
100
|
-
* Stop idle checking
|
|
101
|
-
*/
|
|
92
|
+
|
|
102
93
|
function stopIdleCheck(): void {
|
|
103
94
|
if (idleCheckInterval) {
|
|
104
95
|
clearInterval(idleCheckInterval);
|
|
@@ -108,16 +99,13 @@ function stopIdleCheck(): void {
|
|
|
108
99
|
}
|
|
109
100
|
|
|
110
101
|
export async function createServer(): Promise<Express> {
|
|
111
|
-
// Initialize logger first (sync for startup, async after)
|
|
112
102
|
initializeLogger();
|
|
113
103
|
|
|
114
|
-
// Initialize session for telemetry tracking
|
|
115
104
|
initializeSession();
|
|
116
105
|
|
|
117
106
|
const app = express();
|
|
118
107
|
app.use(express.json());
|
|
119
108
|
|
|
120
|
-
// Track activity for idle restart
|
|
121
109
|
app.use((_req: Request, _res: Response, next: NextFunction) => {
|
|
122
110
|
lastRequestTime = Date.now();
|
|
123
111
|
next();
|
|
@@ -139,7 +127,6 @@ export async function createServer(): Promise<Express> {
|
|
|
139
127
|
uptime: Math.floor(process.uptime()),
|
|
140
128
|
processManager: 'self (detached daemon)',
|
|
141
129
|
pid: process.pid,
|
|
142
|
-
// Idle tracking info
|
|
143
130
|
idle: {
|
|
144
131
|
currentMs: idleTimeMs,
|
|
145
132
|
thresholdMs: MAX_IDLE_TIME_MS,
|
|
@@ -163,11 +150,9 @@ export async function createServer(): Promise<Express> {
|
|
|
163
150
|
});
|
|
164
151
|
});
|
|
165
152
|
|
|
166
|
-
// All tool execution via /tools/call/:toolName (readiness check applied in route files)
|
|
167
153
|
app.use('/tools', toolsRoutes);
|
|
168
154
|
app.use('/prompts', promptsRoutes);
|
|
169
155
|
|
|
170
|
-
// 404 handler for undefined routes
|
|
171
156
|
app.use((_req: Request, res: Response) => {
|
|
172
157
|
res.status(404).json({
|
|
173
158
|
success: false,
|
|
@@ -197,11 +182,7 @@ export async function createServer(): Promise<Express> {
|
|
|
197
182
|
return app;
|
|
198
183
|
}
|
|
199
184
|
|
|
200
|
-
|
|
201
|
-
* Graceful shutdown handler.
|
|
202
|
-
* Triggered by SIGTERM, SIGINT, or idle timeout. Cleans up all resources
|
|
203
|
-
* including PID file, circuit breakers, and HTTP connections.
|
|
204
|
-
*/
|
|
185
|
+
|
|
205
186
|
function gracefulShutdown(signal: string): void {
|
|
206
187
|
if (isShuttingDown) {
|
|
207
188
|
console.log(dimLog(`Already shutting down, ignoring ${signal}`));
|
|
@@ -217,21 +198,16 @@ function gracefulShutdown(signal: string): void {
|
|
|
217
198
|
process.exit(1);
|
|
218
199
|
}, FORCE_EXIT_TIMEOUT_MS).unref();
|
|
219
200
|
|
|
220
|
-
// 1. Remove PID file immediately
|
|
221
201
|
removePidFile();
|
|
222
202
|
|
|
223
|
-
// 2. Stop idle check interval
|
|
224
203
|
stopIdleCheck();
|
|
225
204
|
|
|
226
|
-
// 3. Stop circuit cleanup interval
|
|
227
205
|
stopCircuitCleanup();
|
|
228
206
|
console.log(successLog('✅ Circuit cleanup interval stopped'));
|
|
229
207
|
|
|
230
|
-
// 4. Clear circuit breakers
|
|
231
208
|
clearAllCircuits();
|
|
232
209
|
console.log(successLog('✅ Circuit breakers cleared'));
|
|
233
210
|
|
|
234
|
-
// 5. Close HTTP server (waits for connections to drain)
|
|
235
211
|
if (server) {
|
|
236
212
|
console.log(dimLog('⏳ Waiting for connections to drain...'));
|
|
237
213
|
server.close((err) => {
|
|
@@ -257,16 +233,13 @@ export async function startServer(): Promise<void> {
|
|
|
257
233
|
console.log(agentLog(`🔍 Octocode Research Server running on http://${HOST}:${PORT} (pid: ${process.pid})`));
|
|
258
234
|
console.log(dimLog(`⏳ initializing context...`));
|
|
259
235
|
|
|
260
|
-
// Start background initialization (Warm Start)
|
|
261
236
|
initializeMcpContent()
|
|
262
237
|
.then(() => initializeProviders())
|
|
263
238
|
.then(() => {
|
|
264
239
|
console.log(successLog('✅ Context initialized - Server Ready'));
|
|
265
240
|
|
|
266
|
-
// Reset idle timer after init (prevents early timeout)
|
|
267
241
|
lastRequestTime = Date.now();
|
|
268
242
|
|
|
269
|
-
// Start idle check after initialization
|
|
270
243
|
startIdleCheck();
|
|
271
244
|
|
|
272
245
|
console.log(agentLog(`📁 Logs: ${getLogsPath()}`));
|
|
@@ -283,7 +256,6 @@ export async function startServer(): Promise<void> {
|
|
|
283
256
|
console.log(dimLog(` GET /prompts/list - List prompts`));
|
|
284
257
|
console.log(dimLog(` GET /prompts/info/:name - Get prompt content`));
|
|
285
258
|
|
|
286
|
-
// Log session initialization after server is ready
|
|
287
259
|
fireAndForgetWithTimeout(
|
|
288
260
|
() => logSessionInit(),
|
|
289
261
|
5000,
|
|
@@ -299,7 +271,6 @@ export async function startServer(): Promise<void> {
|
|
|
299
271
|
});
|
|
300
272
|
}
|
|
301
273
|
|
|
302
|
-
// Signal handlers — server is a detached daemon, handles its own signals
|
|
303
274
|
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
304
275
|
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
305
276
|
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type guards for safe error handling.
|
|
3
|
-
*
|
|
4
|
-
* Replaces unsafe type coercion (err as { status?: number }) with
|
|
5
|
-
* proper runtime type checking for better type safety.
|
|
6
|
-
*
|
|
7
|
-
* @module types/errorGuards
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
// =============================================================================
|
|
11
|
-
// Internal Interfaces & Guards
|
|
12
|
-
// =============================================================================
|
|
13
1
|
|
|
14
2
|
interface ErrorWithStatus {
|
|
15
3
|
status: number;
|
|
@@ -48,9 +36,6 @@ function hasMessage(err: unknown): err is { message: string } {
|
|
|
48
36
|
);
|
|
49
37
|
}
|
|
50
38
|
|
|
51
|
-
// =============================================================================
|
|
52
|
-
// Public API
|
|
53
|
-
// =============================================================================
|
|
54
39
|
|
|
55
40
|
export function getErrorStatus(err: unknown): number | undefined {
|
|
56
41
|
return isErrorWithStatus(err) ? err.status : undefined;
|