@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.
Files changed (2) hide show
  1. package/dist/index.js +118 -23
  2. 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 { readFileSync as readFileSync3, readdirSync } from "fs";
839
- import { join as join4, relative } from "path";
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 collectFiles(dir, extensions = [".ts", ".js", ".tsx", ".jsx", ".py", ".go", ".rs", ".java"], maxFiles = 100) {
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
- try {
862
- const content = readFileSync3(fullPath, "utf-8");
863
- files.push({
864
- path: relative(dir, fullPath),
865
- content
866
- });
867
- } catch {
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 files = collectFiles(options.path, void 0, parseInt(options.maxFiles));
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
- plan = JSON.parse(jsonMatch[0]);
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 readFileSync4, existsSync as existsSync2, statSync as statSync2 } from "fs";
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 = readFileSync4(possiblePath, "utf-8");
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(target) && statSync2(target).isFile()) {
1232
+ } else if (existsSync2(safePath) && statSync2(safePath).isFile()) {
1173
1233
  targetPath = target;
1174
- targetContent = readFileSync4(target, "utf-8");
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 readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync2 } from "fs";
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(readFileSync5(CLOUD_CONFIG_PATH, "utf-8"));
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.0",
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.0"
19
+ "@contextos/core": "0.2.3"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/inquirer": "^9.0.7",