cto-ai-cli 5.0.0 → 5.2.0
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 +106 -4
- package/dist/action/index.js +10 -6
- package/dist/api/dashboard.js +10 -6
- package/dist/api/dashboard.js.map +1 -1
- package/dist/api/server.js +86 -29
- package/dist/api/server.js.map +1 -1
- package/dist/cli/gateway.js +60 -46
- package/dist/cli/score.js +2338 -2292
- package/dist/engine/index.d.ts +30 -1
- package/dist/engine/index.js +96 -8
- package/dist/engine/index.js.map +1 -1
- package/dist/gateway/index.d.ts +2 -2
- package/dist/gateway/index.js +60 -46
- package/dist/gateway/index.js.map +1 -1
- package/dist/mcp/v2.js +10 -6
- package/dist/mcp/v2.js.map +1 -1
- package/package.json +1 -1
- package/dist/core/index.d.ts +0 -717
- package/dist/core/index.js +0 -4446
- package/dist/core/index.js.map +0 -1
package/dist/api/server.js
CHANGED
|
@@ -592,8 +592,8 @@ async function analyzeProject(projectPath, config) {
|
|
|
592
592
|
maxDepth: mergedConfig.analysis.maxDepth
|
|
593
593
|
});
|
|
594
594
|
const tokenMethod = mergedConfig.tokens.method;
|
|
595
|
-
const
|
|
596
|
-
|
|
595
|
+
const BATCH_SIZE = 50;
|
|
596
|
+
async function estimateFileTokens(entry) {
|
|
597
597
|
let tokens;
|
|
598
598
|
if (tokenMethod === "tiktoken") {
|
|
599
599
|
try {
|
|
@@ -605,7 +605,7 @@ async function analyzeProject(projectPath, config) {
|
|
|
605
605
|
} else {
|
|
606
606
|
tokens = countTokensChars4(entry.size);
|
|
607
607
|
}
|
|
608
|
-
|
|
608
|
+
return {
|
|
609
609
|
path: entry.path,
|
|
610
610
|
relativePath: entry.relativePath,
|
|
611
611
|
extension: entry.extension,
|
|
@@ -614,16 +614,20 @@ async function analyzeProject(projectPath, config) {
|
|
|
614
614
|
lines: entry.lines,
|
|
615
615
|
lastModified: entry.lastModified,
|
|
616
616
|
kind: classifyFileKind(entry.relativePath),
|
|
617
|
-
// Graph data — populated by graph analysis
|
|
618
617
|
imports: [],
|
|
619
618
|
importedBy: [],
|
|
620
619
|
isHub: false,
|
|
621
620
|
complexity: 0,
|
|
622
|
-
// Risk data — populated by risk analysis
|
|
623
621
|
riskScore: 0,
|
|
624
622
|
riskFactors: [],
|
|
625
623
|
exclusionImpact: "none"
|
|
626
|
-
}
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
const files = [];
|
|
627
|
+
for (let i = 0; i < walkEntries.length; i += BATCH_SIZE) {
|
|
628
|
+
const batch = walkEntries.slice(i, i + BATCH_SIZE);
|
|
629
|
+
const results = await Promise.all(batch.map(estimateFileTokens));
|
|
630
|
+
files.push(...results);
|
|
627
631
|
}
|
|
628
632
|
const graph = buildProjectGraph(absPath, files);
|
|
629
633
|
for (const file of files) {
|
|
@@ -3226,6 +3230,52 @@ async function planInteraction(input) {
|
|
|
3226
3230
|
// src/api/server.ts
|
|
3227
3231
|
var API_VERSION = "1.0.0";
|
|
3228
3232
|
var DEFAULT_PORT = 3141;
|
|
3233
|
+
var FORBIDDEN_PREFIXES = [
|
|
3234
|
+
"/etc",
|
|
3235
|
+
"/usr",
|
|
3236
|
+
"/var",
|
|
3237
|
+
"/sys",
|
|
3238
|
+
"/proc",
|
|
3239
|
+
"/dev",
|
|
3240
|
+
"/boot",
|
|
3241
|
+
"/sbin",
|
|
3242
|
+
"/bin",
|
|
3243
|
+
"/tmp",
|
|
3244
|
+
"/root",
|
|
3245
|
+
"/lib",
|
|
3246
|
+
"/opt"
|
|
3247
|
+
];
|
|
3248
|
+
var FORBIDDEN_PATTERNS = [".ssh", ".gnupg", ".aws", ".env", "passwd", "shadow"];
|
|
3249
|
+
function getAllowedRoots() {
|
|
3250
|
+
const raw = process.env.CTO_ALLOWED_ROOTS;
|
|
3251
|
+
if (!raw) return null;
|
|
3252
|
+
return raw.split(",").map((r) => resolve7(r.trim()));
|
|
3253
|
+
}
|
|
3254
|
+
function validateProjectPath(projectPath) {
|
|
3255
|
+
if (typeof projectPath !== "string" || !projectPath.trim()) {
|
|
3256
|
+
return { valid: false, reason: "Missing or invalid path field" };
|
|
3257
|
+
}
|
|
3258
|
+
const absPath = resolve7(projectPath);
|
|
3259
|
+
const lower = absPath.toLowerCase();
|
|
3260
|
+
for (const prefix of FORBIDDEN_PREFIXES) {
|
|
3261
|
+
if (lower === prefix || lower.startsWith(prefix + "/")) {
|
|
3262
|
+
return { valid: false, reason: `Access denied: system path ${prefix}` };
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
for (const pattern of FORBIDDEN_PATTERNS) {
|
|
3266
|
+
if (lower.includes(pattern)) {
|
|
3267
|
+
return { valid: false, reason: `Access denied: path contains forbidden segment '${pattern}'` };
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
const roots = getAllowedRoots();
|
|
3271
|
+
if (roots && roots.length > 0) {
|
|
3272
|
+
const allowed = roots.some((root) => absPath === root || absPath.startsWith(root + "/"));
|
|
3273
|
+
if (!allowed) {
|
|
3274
|
+
return { valid: false, reason: `Access denied: path not under allowed roots. Set CTO_ALLOWED_ROOTS to allow.` };
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
return { valid: true, absPath };
|
|
3278
|
+
}
|
|
3229
3279
|
function validateApiKey(req) {
|
|
3230
3280
|
const apiKey = process.env.CTO_API_KEY;
|
|
3231
3281
|
if (!apiKey) return true;
|
|
@@ -3287,9 +3337,9 @@ function error(res, status, message) {
|
|
|
3287
3337
|
json(res, status, { error: message, status });
|
|
3288
3338
|
}
|
|
3289
3339
|
async function handleAnalyze(body, res) {
|
|
3290
|
-
const
|
|
3291
|
-
if (!
|
|
3292
|
-
const absPath =
|
|
3340
|
+
const check = validateProjectPath(body.path);
|
|
3341
|
+
if (!check.valid) return error(res, 403, check.reason);
|
|
3342
|
+
const absPath = check.absPath;
|
|
3293
3343
|
const analysis = await getCachedAnalysis(absPath);
|
|
3294
3344
|
json(res, 200, {
|
|
3295
3345
|
project: analysis.projectName,
|
|
@@ -3307,10 +3357,11 @@ async function handleAnalyze(body, res) {
|
|
|
3307
3357
|
});
|
|
3308
3358
|
}
|
|
3309
3359
|
async function handleSelect(body, res) {
|
|
3310
|
-
const {
|
|
3311
|
-
|
|
3360
|
+
const { task, budget } = body;
|
|
3361
|
+
const check = validateProjectPath(body.path);
|
|
3362
|
+
if (!check.valid) return error(res, 403, check.reason);
|
|
3312
3363
|
if (!task) return error(res, 400, "Missing required field: task");
|
|
3313
|
-
const absPath =
|
|
3364
|
+
const absPath = check.absPath;
|
|
3314
3365
|
const analysis = await getCachedAnalysis(absPath);
|
|
3315
3366
|
const selection = await selectContext({
|
|
3316
3367
|
task,
|
|
@@ -3334,9 +3385,10 @@ async function handleSelect(body, res) {
|
|
|
3334
3385
|
});
|
|
3335
3386
|
}
|
|
3336
3387
|
async function handleScore(body, res) {
|
|
3337
|
-
const {
|
|
3338
|
-
|
|
3339
|
-
|
|
3388
|
+
const { task, budget } = body;
|
|
3389
|
+
const check = validateProjectPath(body.path);
|
|
3390
|
+
if (!check.valid) return error(res, 403, check.reason);
|
|
3391
|
+
const absPath = check.absPath;
|
|
3340
3392
|
const analysis = await getCachedAnalysis(absPath);
|
|
3341
3393
|
const score = await computeContextScore(
|
|
3342
3394
|
analysis,
|
|
@@ -3359,9 +3411,10 @@ async function handleScore(body, res) {
|
|
|
3359
3411
|
});
|
|
3360
3412
|
}
|
|
3361
3413
|
async function handleBenchmark(body, res) {
|
|
3362
|
-
const {
|
|
3363
|
-
|
|
3364
|
-
|
|
3414
|
+
const { task, budget } = body;
|
|
3415
|
+
const check = validateProjectPath(body.path);
|
|
3416
|
+
if (!check.valid) return error(res, 403, check.reason);
|
|
3417
|
+
const absPath = check.absPath;
|
|
3365
3418
|
const analysis = await getCachedAnalysis(absPath);
|
|
3366
3419
|
const result = await runBenchmark(
|
|
3367
3420
|
analysis,
|
|
@@ -3371,10 +3424,11 @@ async function handleBenchmark(body, res) {
|
|
|
3371
3424
|
json(res, 200, result);
|
|
3372
3425
|
}
|
|
3373
3426
|
async function handleQuality(body, res) {
|
|
3374
|
-
const {
|
|
3375
|
-
|
|
3427
|
+
const { task, budget } = body;
|
|
3428
|
+
const check = validateProjectPath(body.path);
|
|
3429
|
+
if (!check.valid) return error(res, 403, check.reason);
|
|
3376
3430
|
if (!task) return error(res, 400, "Missing required field: task");
|
|
3377
|
-
const absPath =
|
|
3431
|
+
const absPath = check.absPath;
|
|
3378
3432
|
const analysis = await getCachedAnalysis(absPath);
|
|
3379
3433
|
const result = await runQualityBenchmark(analysis, task, budget ?? 5e4);
|
|
3380
3434
|
json(res, 200, {
|
|
@@ -3388,9 +3442,10 @@ async function handleQuality(body, res) {
|
|
|
3388
3442
|
});
|
|
3389
3443
|
}
|
|
3390
3444
|
async function handlePRContext(body, res) {
|
|
3391
|
-
const {
|
|
3392
|
-
|
|
3393
|
-
|
|
3445
|
+
const { baseBranch, depth, includeTests } = body;
|
|
3446
|
+
const check = validateProjectPath(body.path);
|
|
3447
|
+
if (!check.valid) return error(res, 403, check.reason);
|
|
3448
|
+
const absPath = check.absPath;
|
|
3394
3449
|
const analysis = await getCachedAnalysis(absPath);
|
|
3395
3450
|
const pr = await generatePRContext(analysis, {
|
|
3396
3451
|
baseBranch: baseBranch ?? "main",
|
|
@@ -3409,10 +3464,11 @@ async function handlePRContext(body, res) {
|
|
|
3409
3464
|
});
|
|
3410
3465
|
}
|
|
3411
3466
|
async function handleInteract(body, res) {
|
|
3412
|
-
const {
|
|
3413
|
-
|
|
3467
|
+
const { task, budget, model } = body;
|
|
3468
|
+
const check = validateProjectPath(body.path);
|
|
3469
|
+
if (!check.valid) return error(res, 403, check.reason);
|
|
3414
3470
|
if (!task) return error(res, 400, "Missing required field: task");
|
|
3415
|
-
const absPath =
|
|
3471
|
+
const absPath = check.absPath;
|
|
3416
3472
|
const analysis = await getCachedAnalysis(absPath);
|
|
3417
3473
|
const plan = await planInteraction({
|
|
3418
3474
|
task,
|
|
@@ -3568,8 +3624,9 @@ function createAPIServer() {
|
|
|
3568
3624
|
const body = req.method === "GET" ? {} : await readBody(req);
|
|
3569
3625
|
await handler(body, res);
|
|
3570
3626
|
} catch (err) {
|
|
3571
|
-
|
|
3572
|
-
error(
|
|
3627
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3628
|
+
console.error(`[CTO API] Error on ${routeKey}:`, message);
|
|
3629
|
+
error(res, 500, message || "Internal server error");
|
|
3573
3630
|
}
|
|
3574
3631
|
});
|
|
3575
3632
|
return server2;
|