@contextos/cli 0.2.0 → 0.2.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/dist/index.js +118 -23
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command as Command14 } from "commander";
|
|
5
|
+
import { join as join7 } from "path";
|
|
6
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, appendFileSync } from "fs";
|
|
5
7
|
|
|
6
8
|
// src/commands/init.ts
|
|
7
9
|
import { Command } from "commander";
|
|
@@ -835,18 +837,36 @@ function parseValue(value) {
|
|
|
835
837
|
import { Command as Command10 } from "commander";
|
|
836
838
|
import chalk10 from "chalk";
|
|
837
839
|
import ora9 from "ora";
|
|
838
|
-
import {
|
|
839
|
-
import {
|
|
840
|
+
import { readdirSync } from "fs";
|
|
841
|
+
import { readFile } from "fs/promises";
|
|
842
|
+
import { join as join4, relative, resolve, normalize } from "path";
|
|
840
843
|
import {
|
|
841
844
|
RLMEngine,
|
|
842
845
|
createGeminiClient as createGeminiClient3,
|
|
843
846
|
mergeFilesToContext
|
|
844
847
|
} from "@contextos/core";
|
|
845
|
-
function
|
|
848
|
+
function validatePath(userPath, projectRoot) {
|
|
849
|
+
const resolved = resolve(projectRoot, userPath);
|
|
850
|
+
const normalized = normalize(resolved);
|
|
851
|
+
const rootNormalized = normalize(projectRoot);
|
|
852
|
+
if (!normalized.startsWith(rootNormalized)) {
|
|
853
|
+
throw new Error(
|
|
854
|
+
`Invalid path: "${userPath}" escapes project boundaries.
|
|
855
|
+
Path must be within: ${rootNormalized}`
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
return normalized;
|
|
859
|
+
}
|
|
860
|
+
async function collectFiles(dir, extensions = [".ts", ".js", ".tsx", ".jsx", ".py", ".go", ".rs", ".java"], maxFiles = 100, maxDepth = 20) {
|
|
846
861
|
const files = [];
|
|
847
|
-
function walk(currentDir) {
|
|
862
|
+
async function walk(currentDir, depth) {
|
|
863
|
+
if (depth > maxDepth) {
|
|
864
|
+
console.warn(`Maximum depth (${maxDepth}) reached at ${currentDir}. Skipping deeper directories.`);
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
848
867
|
if (files.length >= maxFiles) return;
|
|
849
868
|
const entries = readdirSync(currentDir, { withFileTypes: true });
|
|
869
|
+
const filePromises = [];
|
|
850
870
|
for (const entry of entries) {
|
|
851
871
|
if (files.length >= maxFiles) break;
|
|
852
872
|
const fullPath = join4(currentDir, entry.name);
|
|
@@ -854,32 +874,43 @@ function collectFiles(dir, extensions = [".ts", ".js", ".tsx", ".jsx", ".py", ".
|
|
|
854
874
|
if (["node_modules", ".git", "dist", "build", ".next", "__pycache__", "venv"].includes(entry.name)) {
|
|
855
875
|
continue;
|
|
856
876
|
}
|
|
857
|
-
walk(fullPath);
|
|
877
|
+
await walk(fullPath, depth + 1);
|
|
858
878
|
} else if (entry.isFile()) {
|
|
859
879
|
const ext = entry.name.substring(entry.name.lastIndexOf("."));
|
|
860
880
|
if (extensions.includes(ext)) {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
881
|
+
const promise = (async () => {
|
|
882
|
+
try {
|
|
883
|
+
const content = await readFile(fullPath, "utf-8");
|
|
884
|
+
files.push({
|
|
885
|
+
path: relative(dir, fullPath),
|
|
886
|
+
content
|
|
887
|
+
});
|
|
888
|
+
} catch {
|
|
889
|
+
}
|
|
890
|
+
})();
|
|
891
|
+
filePromises.push(promise);
|
|
892
|
+
if (filePromises.length >= 10) {
|
|
893
|
+
await Promise.all(filePromises);
|
|
894
|
+
filePromises.length = 0;
|
|
868
895
|
}
|
|
869
896
|
}
|
|
870
897
|
}
|
|
871
898
|
}
|
|
899
|
+
if (filePromises.length > 0) {
|
|
900
|
+
await Promise.all(filePromises);
|
|
901
|
+
}
|
|
872
902
|
}
|
|
873
|
-
walk(dir);
|
|
903
|
+
await walk(dir, 0);
|
|
874
904
|
return files;
|
|
875
905
|
}
|
|
876
|
-
var analyzeCommand = new Command10("analyze").description("RLM-powered deep analysis of your codebase").argument("<goal>", 'Analysis goal or question (e.g., "Find all API endpoints")').option("-d, --depth <number>", "Maximum recursion depth for RLM", "3").option("-b, --budget <tokens>", "Token budget for analysis", "50000").option("-p, --path <path>", "Path to analyze (default: current directory)", ".").option("--max-files <number>", "Maximum files to include", "100").option("--verbose", "Show detailed execution trace").action(async (goal, options) => {
|
|
906
|
+
var analyzeCommand = new Command10("analyze").description("RLM-powered deep analysis of your codebase").argument("<goal>", 'Analysis goal or question (e.g., "Find all API endpoints")').option("-d, --depth <number>", "Maximum recursion depth for RLM", "3").option("-b, --budget <tokens>", "Token budget for analysis", "50000").option("-p, --path <path>", "Path to analyze (default: current directory)", ".").option("--max-files <number>", "Maximum files to include", "100").option("--max-depth <number>", "Maximum directory depth to traverse", "20").option("--verbose", "Show detailed execution trace").action(async (goal, options) => {
|
|
877
907
|
console.log(chalk10.blue.bold("\n\u{1F50D} RLM Analysis Engine\n"));
|
|
878
908
|
console.log(chalk10.gray("Goal: ") + chalk10.white(goal));
|
|
879
909
|
console.log();
|
|
880
910
|
const spinner = ora9("Collecting codebase files...").start();
|
|
881
911
|
try {
|
|
882
|
-
const
|
|
912
|
+
const safePath = validatePath(options.path, process.cwd());
|
|
913
|
+
const files = await collectFiles(safePath, void 0, parseInt(options.maxFiles), parseInt(options.maxDepth));
|
|
883
914
|
spinner.text = `Found ${files.length} files. Building context...`;
|
|
884
915
|
if (files.length === 0) {
|
|
885
916
|
spinner.fail("No source files found");
|
|
@@ -1079,7 +1110,16 @@ Respond in JSON format:
|
|
|
1079
1110
|
try {
|
|
1080
1111
|
const jsonMatch = result.answer.match(/\{[\s\S]*\}/);
|
|
1081
1112
|
if (jsonMatch) {
|
|
1082
|
-
|
|
1113
|
+
try {
|
|
1114
|
+
plan = JSON.parse(jsonMatch[0]);
|
|
1115
|
+
} catch (parseError) {
|
|
1116
|
+
spinner.warn("Failed to parse refactoring plan JSON");
|
|
1117
|
+
console.log();
|
|
1118
|
+
console.log(chalk11.blue.bold("Analysis Result:"));
|
|
1119
|
+
console.log(result.answer);
|
|
1120
|
+
console.log();
|
|
1121
|
+
process.exit(0);
|
|
1122
|
+
}
|
|
1083
1123
|
} else {
|
|
1084
1124
|
throw new Error("No JSON found in response");
|
|
1085
1125
|
}
|
|
@@ -1149,13 +1189,22 @@ Respond in JSON format:
|
|
|
1149
1189
|
import { Command as Command12 } from "commander";
|
|
1150
1190
|
import chalk12 from "chalk";
|
|
1151
1191
|
import ora11 from "ora";
|
|
1152
|
-
import { readFileSync as
|
|
1153
|
-
import { join as join5, basename } from "path";
|
|
1192
|
+
import { readFileSync as readFileSync3, existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
1193
|
+
import { join as join5, basename, resolve as resolve2, normalize as normalize2 } from "path";
|
|
1154
1194
|
import {
|
|
1155
1195
|
RLMEngine as RLMEngine3,
|
|
1156
1196
|
createGeminiClient as createGeminiClient5,
|
|
1157
1197
|
mergeFilesToContext as mergeFilesToContext3
|
|
1158
1198
|
} from "@contextos/core";
|
|
1199
|
+
function validatePath2(userPath, projectRoot) {
|
|
1200
|
+
const resolved = resolve2(projectRoot, userPath);
|
|
1201
|
+
const normalized = normalize2(resolved);
|
|
1202
|
+
const rootNormalized = normalize2(projectRoot);
|
|
1203
|
+
if (!normalized.startsWith(rootNormalized)) {
|
|
1204
|
+
throw new Error(`Path traversal detected: "${userPath}" escapes project boundaries`);
|
|
1205
|
+
}
|
|
1206
|
+
return normalized;
|
|
1207
|
+
}
|
|
1159
1208
|
var explainCommand = new Command12("explain").description("Get AI-powered explanation of code").argument("<target>", "File path, function name, or module to explain").option("-d, --depth <number>", "Include dependencies up to depth", "1").option("-f, --format <format>", "Output format: text, markdown, json", "markdown").option("--no-examples", "Skip usage examples in explanation").option("--technical", "Include technical implementation details").action(async (target, options) => {
|
|
1160
1209
|
console.log(chalk12.blue.bold("\n\u{1F4DA} ContextOS Code Explainer\n"));
|
|
1161
1210
|
const spinner = ora11("Analyzing target...").start();
|
|
@@ -1163,15 +1212,26 @@ var explainCommand = new Command12("explain").description("Get AI-powered explan
|
|
|
1163
1212
|
let targetContent = "";
|
|
1164
1213
|
let targetPath = "";
|
|
1165
1214
|
let contextFiles = [];
|
|
1215
|
+
let safePath;
|
|
1216
|
+
try {
|
|
1217
|
+
safePath = validatePath2(target, process.cwd());
|
|
1218
|
+
} catch (error) {
|
|
1219
|
+
if (error instanceof Error && error.message.includes("Path traversal")) {
|
|
1220
|
+
spinner.fail("Invalid path");
|
|
1221
|
+
console.log(chalk12.red("Error:"), error.message);
|
|
1222
|
+
process.exit(1);
|
|
1223
|
+
}
|
|
1224
|
+
throw error;
|
|
1225
|
+
}
|
|
1166
1226
|
const possiblePath = join5(process.cwd(), target);
|
|
1167
1227
|
if (existsSync2(possiblePath) && statSync2(possiblePath).isFile()) {
|
|
1168
1228
|
targetPath = target;
|
|
1169
|
-
targetContent =
|
|
1229
|
+
targetContent = readFileSync3(possiblePath, "utf-8");
|
|
1170
1230
|
contextFiles.push({ path: target, content: targetContent });
|
|
1171
1231
|
spinner.text = `Found file: ${target}`;
|
|
1172
|
-
} else if (existsSync2(
|
|
1232
|
+
} else if (existsSync2(safePath) && statSync2(safePath).isFile()) {
|
|
1173
1233
|
targetPath = target;
|
|
1174
|
-
targetContent =
|
|
1234
|
+
targetContent = readFileSync3(safePath, "utf-8");
|
|
1175
1235
|
contextFiles.push({ path: basename(target), content: targetContent });
|
|
1176
1236
|
spinner.text = `Found file: ${target}`;
|
|
1177
1237
|
} else {
|
|
@@ -1738,7 +1798,7 @@ ${chalk15.bold("Date Range:")}`);
|
|
|
1738
1798
|
// src/commands/cloud.ts
|
|
1739
1799
|
import chalk16 from "chalk";
|
|
1740
1800
|
import ora15 from "ora";
|
|
1741
|
-
import { existsSync as existsSync3, readFileSync as
|
|
1801
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
1742
1802
|
import { homedir } from "os";
|
|
1743
1803
|
import { join as join6 } from "path";
|
|
1744
1804
|
var CLOUD_CONFIG_PATH = join6(homedir(), ".contextos", "cloud.json");
|
|
@@ -1746,7 +1806,7 @@ var DEFAULT_CLOUD_URL = "https://api.contextos.dev";
|
|
|
1746
1806
|
function loadCloudConfig() {
|
|
1747
1807
|
if (existsSync3(CLOUD_CONFIG_PATH)) {
|
|
1748
1808
|
try {
|
|
1749
|
-
return JSON.parse(
|
|
1809
|
+
return JSON.parse(readFileSync4(CLOUD_CONFIG_PATH, "utf-8"));
|
|
1750
1810
|
} catch {
|
|
1751
1811
|
return { cloudUrl: DEFAULT_CLOUD_URL };
|
|
1752
1812
|
}
|
|
@@ -1861,6 +1921,41 @@ function registerCloudCommands(program2) {
|
|
|
1861
1921
|
}
|
|
1862
1922
|
|
|
1863
1923
|
// src/index.ts
|
|
1924
|
+
var crashLogDir = join7(process.cwd(), ".contextos");
|
|
1925
|
+
if (!existsSync4(crashLogDir)) {
|
|
1926
|
+
mkdirSync3(crashLogDir, { recursive: true });
|
|
1927
|
+
}
|
|
1928
|
+
var crashLogPath = join7(crashLogDir, "crash.log");
|
|
1929
|
+
function logCrash(message, data) {
|
|
1930
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1931
|
+
const logEntry = `[${timestamp}] ${message}${data ? `
|
|
1932
|
+
${data}
|
|
1933
|
+
|
|
1934
|
+
` : "\n\n"}`;
|
|
1935
|
+
console.error(logEntry);
|
|
1936
|
+
appendFileSync(crashLogPath, logEntry, "utf-8");
|
|
1937
|
+
}
|
|
1938
|
+
process.on("uncaughtException", (error) => {
|
|
1939
|
+
console.error("\u274C UNCAUGHT EXCEPTION:", error.message);
|
|
1940
|
+
console.error("Stack:", error.stack);
|
|
1941
|
+
if (process.env.NODE_ENV === "production") {
|
|
1942
|
+
logCrash(`UNCAUGHT EXCEPTION: ${error.message}`, error.stack);
|
|
1943
|
+
}
|
|
1944
|
+
setTimeout(() => process.exit(1), 1e3);
|
|
1945
|
+
});
|
|
1946
|
+
process.on("unhandledRejection", (reason) => {
|
|
1947
|
+
console.error("\u274C UNHANDLED REJECTION:", reason);
|
|
1948
|
+
if (process.env.NODE_ENV === "production") {
|
|
1949
|
+
const reasonStr = reason instanceof Error ? reason.stack : String(reason);
|
|
1950
|
+
logCrash("UNHANDLED REJECTION", reasonStr);
|
|
1951
|
+
}
|
|
1952
|
+
});
|
|
1953
|
+
process.on("multipleResolves", (type, promise) => {
|
|
1954
|
+
console.warn("\u26A0\uFE0F MULTIPLE RESOLVES:", type, promise);
|
|
1955
|
+
});
|
|
1956
|
+
process.on("warning", (warning) => {
|
|
1957
|
+
console.warn("\u26A0\uFE0F PROCESS WARNING:", warning.name, warning.message);
|
|
1958
|
+
});
|
|
1864
1959
|
var program = new Command14();
|
|
1865
1960
|
program.name("ctx").description("ContextOS - The Context Server Protocol for AI Coding").version("0.1.0").option("-v, --verbose", "Enable verbose output");
|
|
1866
1961
|
program.addCommand(initCommand);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contextos/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "CLI for ContextOS - The Context Server Protocol for AI Coding",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"inquirer": "^9.2.0",
|
|
17
17
|
"ora": "^8.0.0",
|
|
18
18
|
"yaml": "^2.4.0",
|
|
19
|
-
"@contextos/core": "0.2.
|
|
19
|
+
"@contextos/core": "0.2.3"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/inquirer": "^9.0.7",
|