@eloquence98/ctx 0.1.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 +138 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +107 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +24 -0
- package/dist/formatters/index.d.ts +1 -0
- package/dist/formatters/index.js +1 -0
- package/dist/formatters/markdown.d.ts +2 -0
- package/dist/formatters/markdown.js +73 -0
- package/dist/parser.d.ts +2 -0
- package/dist/parser.js +73 -0
- package/dist/scanner.d.ts +3 -0
- package/dist/scanner.js +73 -0
- package/dist/types.d.ts +23 -0
- package/dist/types.js +1 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# ctx
|
|
2
|
+
|
|
3
|
+
Generate AI-ready context from your codebase in seconds.
|
|
4
|
+
|
|
5
|
+
`ctx` scans a project directory and produces a clean, structured overview of folders and exported symbols. The output is designed to be pasted directly into AI tools to give them accurate context about your codebase.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Why ctx
|
|
10
|
+
|
|
11
|
+
AI assistants do not understand your project unless you explain it.
|
|
12
|
+
|
|
13
|
+
Without ctx, you usually have to:
|
|
14
|
+
|
|
15
|
+
- Manually copy files
|
|
16
|
+
- Describe folder structure
|
|
17
|
+
- Explain what exists and how things connect
|
|
18
|
+
|
|
19
|
+
`ctx` automates this by generating a concise snapshot of your project with a single command.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## What It Does
|
|
24
|
+
|
|
25
|
+
- Recursively scans a directory
|
|
26
|
+
- Detects common project conventions
|
|
27
|
+
- Extracts exported symbols
|
|
28
|
+
- Produces readable, AI-friendly output
|
|
29
|
+
- Requires zero configuration
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
### No install (recommended)
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx @eloquence98/ctx ./src
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Global install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install -g @eloquence98/ctx
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Local install (dev dependency)
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install --save-dev @eloquence98/ctx
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
### Scan a directory
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
ctx ./src
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Scan current directory
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
ctx .
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Output as JSON
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
ctx ./src -o json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Write output to a file
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
ctx ./src > CONTEXT.md
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## What Gets Extracted
|
|
84
|
+
|
|
85
|
+
### Code Symbols
|
|
86
|
+
|
|
87
|
+
- Functions
|
|
88
|
+
- Constants
|
|
89
|
+
- Types
|
|
90
|
+
- Interfaces
|
|
91
|
+
|
|
92
|
+
### Project Structure
|
|
93
|
+
|
|
94
|
+
- Routes (App Router / Pages Router)
|
|
95
|
+
- Features or modules
|
|
96
|
+
- Components
|
|
97
|
+
- Hooks
|
|
98
|
+
- Utilities and libraries
|
|
99
|
+
|
|
100
|
+
Detection is convention-based and works with most modern JavaScript and TypeScript projects.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Use Cases
|
|
105
|
+
|
|
106
|
+
- Providing context to ChatGPT, Claude, or Copilot
|
|
107
|
+
- Generating architecture documentation
|
|
108
|
+
- Onboarding new team members
|
|
109
|
+
- Understanding unfamiliar codebases
|
|
110
|
+
- Preparing projects for AI-assisted refactors
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Output Philosophy
|
|
115
|
+
|
|
116
|
+
The output is:
|
|
117
|
+
|
|
118
|
+
- Minimal
|
|
119
|
+
- Structured
|
|
120
|
+
- Human-readable
|
|
121
|
+
- Optimized for AI comprehension
|
|
122
|
+
|
|
123
|
+
Only high-signal information is included.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Roadmap
|
|
128
|
+
|
|
129
|
+
- Clipboard support (`--copy`)
|
|
130
|
+
- Config file support
|
|
131
|
+
- VS Code extension
|
|
132
|
+
- Watch mode
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from "commander";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
import { scanDirectory, getRoutes } from "./scanner.js";
|
|
6
|
+
import { parseFile } from "./parser.js";
|
|
7
|
+
import { formatMarkdown } from "./formatters/index.js";
|
|
8
|
+
program
|
|
9
|
+
.name("ctx")
|
|
10
|
+
.description("Generate AI-ready context from your codebase")
|
|
11
|
+
.version("0.1.0")
|
|
12
|
+
.argument("[path]", "Path to scan", "./src")
|
|
13
|
+
.option("-o, --output <format>", "Output format: md, json", "md")
|
|
14
|
+
.action(async (targetPath, options) => {
|
|
15
|
+
const absolutePath = path.resolve(process.cwd(), targetPath);
|
|
16
|
+
console.log(`\n📁 Scanning ${absolutePath}...\n`);
|
|
17
|
+
try {
|
|
18
|
+
// Scan all files
|
|
19
|
+
const files = await scanDirectory(absolutePath);
|
|
20
|
+
if (files.length === 0) {
|
|
21
|
+
console.log("No files found. Check your path.");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
// Parse all files
|
|
25
|
+
const parsedFiles = await Promise.all(files.map((file) => parseFile(file)));
|
|
26
|
+
// Get routes - try to find app directory
|
|
27
|
+
let routes = [];
|
|
28
|
+
const appDir = await findAppDirectory(absolutePath);
|
|
29
|
+
if (appDir) {
|
|
30
|
+
routes = await getRoutes(appDir);
|
|
31
|
+
}
|
|
32
|
+
// Build context
|
|
33
|
+
const context = {
|
|
34
|
+
routes,
|
|
35
|
+
features: groupByFolder(parsedFiles, absolutePath, "features"),
|
|
36
|
+
hooks: getHooks(parsedFiles),
|
|
37
|
+
lib: groupByFolder(parsedFiles, absolutePath, "lib"),
|
|
38
|
+
components: groupByFolder(parsedFiles, absolutePath, "components"),
|
|
39
|
+
};
|
|
40
|
+
// Output
|
|
41
|
+
if (options.output === "json") {
|
|
42
|
+
console.log(JSON.stringify(contextToJSON(context), null, 2));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
console.log(formatMarkdown(context, absolutePath));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error("Error:", error);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
async function findAppDirectory(basePath) {
|
|
54
|
+
const possiblePaths = [
|
|
55
|
+
path.join(basePath, "app"),
|
|
56
|
+
path.join(basePath, "src", "app"),
|
|
57
|
+
path.join(basePath, "src/app"),
|
|
58
|
+
];
|
|
59
|
+
for (const p of possiblePaths) {
|
|
60
|
+
try {
|
|
61
|
+
const stat = await fs.stat(p);
|
|
62
|
+
if (stat.isDirectory()) {
|
|
63
|
+
return p;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Path doesn't exist, try next
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
function groupByFolder(files, basePath, folderName) {
|
|
73
|
+
const grouped = new Map();
|
|
74
|
+
for (const file of files) {
|
|
75
|
+
const relativePath = path.relative(basePath, file.filePath);
|
|
76
|
+
const parts = relativePath.split(path.sep);
|
|
77
|
+
const folderIndex = parts.indexOf(folderName);
|
|
78
|
+
if (folderIndex === -1)
|
|
79
|
+
continue;
|
|
80
|
+
const remainingParts = parts.slice(folderIndex + 1);
|
|
81
|
+
// Need at least 2 parts: subfolder + filename
|
|
82
|
+
if (remainingParts.length < 2)
|
|
83
|
+
continue;
|
|
84
|
+
const subFolder = remainingParts[0];
|
|
85
|
+
if (!grouped.has(subFolder)) {
|
|
86
|
+
grouped.set(subFolder, []);
|
|
87
|
+
}
|
|
88
|
+
grouped.get(subFolder).push(file);
|
|
89
|
+
}
|
|
90
|
+
return grouped;
|
|
91
|
+
}
|
|
92
|
+
function getHooks(files) {
|
|
93
|
+
return files.filter((f) => f.filePath.includes("/hooks/") ||
|
|
94
|
+
f.filePath.includes("\\hooks\\") ||
|
|
95
|
+
f.fileName.startsWith("use-") ||
|
|
96
|
+
f.fileName.startsWith("use."));
|
|
97
|
+
}
|
|
98
|
+
function contextToJSON(context) {
|
|
99
|
+
return {
|
|
100
|
+
routes: context.routes,
|
|
101
|
+
features: Object.fromEntries(context.features),
|
|
102
|
+
hooks: context.hooks,
|
|
103
|
+
lib: Object.fromEntries(context.lib),
|
|
104
|
+
components: Object.fromEntries(context.components),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
program.parse();
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const defaultConfig = {
|
|
2
|
+
entry: "./src",
|
|
3
|
+
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
|
4
|
+
ignore: [
|
|
5
|
+
"node_modules",
|
|
6
|
+
".git",
|
|
7
|
+
"dist",
|
|
8
|
+
"build",
|
|
9
|
+
".next",
|
|
10
|
+
"*.test.*",
|
|
11
|
+
"*.spec.*",
|
|
12
|
+
"__tests__",
|
|
13
|
+
"*.d.ts",
|
|
14
|
+
".DS_Store",
|
|
15
|
+
],
|
|
16
|
+
maxDepth: 10,
|
|
17
|
+
};
|
|
18
|
+
export const folderCategories = {
|
|
19
|
+
routes: ["app", "pages"],
|
|
20
|
+
features: ["features", "modules", "domains"],
|
|
21
|
+
hooks: ["hooks"],
|
|
22
|
+
lib: ["lib", "utils", "helpers", "services"],
|
|
23
|
+
components: ["components", "ui"],
|
|
24
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { formatMarkdown } from "./markdown.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { formatMarkdown } from "./markdown.js";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export function formatMarkdown(data, basePath) {
|
|
2
|
+
let output = "";
|
|
3
|
+
// Routes section
|
|
4
|
+
if (data.routes.length > 0) {
|
|
5
|
+
output += `=== ROUTES (src/app) ===\n\n`;
|
|
6
|
+
for (const route of data.routes) {
|
|
7
|
+
output += `${route}\n`;
|
|
8
|
+
}
|
|
9
|
+
output += "\n";
|
|
10
|
+
}
|
|
11
|
+
// Features section
|
|
12
|
+
if (data.features.size > 0) {
|
|
13
|
+
output += `=== FEATURES ===\n\n`;
|
|
14
|
+
output += formatFolderGroup(data.features);
|
|
15
|
+
}
|
|
16
|
+
// Components section
|
|
17
|
+
if (data.components.size > 0) {
|
|
18
|
+
output += `=== COMPONENTS ===\n\n`;
|
|
19
|
+
output += formatFolderGroup(data.components);
|
|
20
|
+
}
|
|
21
|
+
// Hooks section
|
|
22
|
+
if (data.hooks.length > 0) {
|
|
23
|
+
output += `=== HOOKS ===\n\n`;
|
|
24
|
+
for (const file of data.hooks) {
|
|
25
|
+
output += formatSingleFile(file);
|
|
26
|
+
}
|
|
27
|
+
output += "\n";
|
|
28
|
+
}
|
|
29
|
+
// Lib section
|
|
30
|
+
if (data.lib.size > 0) {
|
|
31
|
+
output += `=== LIB ===\n\n`;
|
|
32
|
+
output += formatFolderGroup(data.lib);
|
|
33
|
+
}
|
|
34
|
+
output += `=== DONE ===\n`;
|
|
35
|
+
return output;
|
|
36
|
+
}
|
|
37
|
+
function formatFolderGroup(folders) {
|
|
38
|
+
let output = "";
|
|
39
|
+
for (const [folderName, files] of folders) {
|
|
40
|
+
output += `${folderName}/\n`;
|
|
41
|
+
for (const file of files) {
|
|
42
|
+
output += formatSingleFile(file);
|
|
43
|
+
}
|
|
44
|
+
output += "\n";
|
|
45
|
+
}
|
|
46
|
+
return output;
|
|
47
|
+
}
|
|
48
|
+
function formatSingleFile(file) {
|
|
49
|
+
const hasExports = file.functions.length > 0 ||
|
|
50
|
+
file.constants.length > 0 ||
|
|
51
|
+
file.types.length > 0 ||
|
|
52
|
+
file.interfaces.length > 0 ||
|
|
53
|
+
file.classes.length > 0;
|
|
54
|
+
if (!hasExports)
|
|
55
|
+
return "";
|
|
56
|
+
let output = `${file.fileName}\n`;
|
|
57
|
+
for (const fn of file.functions) {
|
|
58
|
+
output += `• function: ${fn}\n`;
|
|
59
|
+
}
|
|
60
|
+
for (const constant of file.constants) {
|
|
61
|
+
output += `• constant: ${constant}\n`;
|
|
62
|
+
}
|
|
63
|
+
for (const type of file.types) {
|
|
64
|
+
output += `• type: ${type}\n`;
|
|
65
|
+
}
|
|
66
|
+
for (const iface of file.interfaces) {
|
|
67
|
+
output += `• interface: ${iface}\n`;
|
|
68
|
+
}
|
|
69
|
+
for (const cls of file.classes) {
|
|
70
|
+
output += `• class: ${cls}\n`;
|
|
71
|
+
}
|
|
72
|
+
return output;
|
|
73
|
+
}
|
package/dist/parser.d.ts
ADDED
package/dist/parser.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
export async function parseFile(filePath) {
|
|
4
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
5
|
+
const fileName = path.basename(filePath);
|
|
6
|
+
return {
|
|
7
|
+
filePath,
|
|
8
|
+
fileName,
|
|
9
|
+
functions: extractFunctions(content),
|
|
10
|
+
constants: extractConstants(content),
|
|
11
|
+
types: extractTypes(content),
|
|
12
|
+
interfaces: extractInterfaces(content),
|
|
13
|
+
classes: extractClasses(content),
|
|
14
|
+
defaultExport: extractDefaultExport(content),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function extractFunctions(content) {
|
|
18
|
+
const functions = [];
|
|
19
|
+
// export function name
|
|
20
|
+
const funcMatches = content.matchAll(/export\s+(?:async\s+)?function\s+(\w+)/g);
|
|
21
|
+
for (const match of funcMatches) {
|
|
22
|
+
functions.push(match[1]);
|
|
23
|
+
}
|
|
24
|
+
// export const name = async? (...) => or function(
|
|
25
|
+
const arrowMatches = content.matchAll(/export\s+const\s+(\w+)\s*=\s*(?:async\s*)?(?:\([^)]*\)|[a-zA-Z_]\w*)\s*(?:=>|\{)/g);
|
|
26
|
+
for (const match of arrowMatches) {
|
|
27
|
+
if (!functions.includes(match[1])) {
|
|
28
|
+
functions.push(match[1]);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return functions;
|
|
32
|
+
}
|
|
33
|
+
function extractConstants(content) {
|
|
34
|
+
const constants = [];
|
|
35
|
+
// Match export const that are NOT functions
|
|
36
|
+
const lines = content.split("\n");
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
// export const NAME = value (not a function)
|
|
39
|
+
const match = line.match(/export\s+const\s+(\w+)\s*=\s*(?!(?:async\s*)?(?:\(|function|\w+\s*=>))/);
|
|
40
|
+
if (match) {
|
|
41
|
+
constants.push(match[1]);
|
|
42
|
+
}
|
|
43
|
+
// Also catch: export const NAME: Type =
|
|
44
|
+
const typedMatch = line.match(/export\s+const\s+(\w+)\s*:\s*[^=]+=\s*(?!(?:async\s*)?(?:\(|function|\w+\s*=>))/);
|
|
45
|
+
if (typedMatch && !constants.includes(typedMatch[1])) {
|
|
46
|
+
constants.push(typedMatch[1]);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return constants;
|
|
50
|
+
}
|
|
51
|
+
function extractTypes(content) {
|
|
52
|
+
const matches = content.matchAll(/export\s+type\s+(\w+)/g);
|
|
53
|
+
return [...matches].map((m) => m[1]);
|
|
54
|
+
}
|
|
55
|
+
function extractInterfaces(content) {
|
|
56
|
+
const matches = content.matchAll(/export\s+interface\s+(\w+)/g);
|
|
57
|
+
return [...matches].map((m) => m[1]);
|
|
58
|
+
}
|
|
59
|
+
function extractClasses(content) {
|
|
60
|
+
const matches = content.matchAll(/export\s+(?:default\s+)?class\s+(\w+)/g);
|
|
61
|
+
return [...matches].map((m) => m[1]);
|
|
62
|
+
}
|
|
63
|
+
function extractDefaultExport(content) {
|
|
64
|
+
// export default function Name
|
|
65
|
+
const funcMatch = content.match(/export\s+default\s+function\s+(\w+)/);
|
|
66
|
+
if (funcMatch)
|
|
67
|
+
return funcMatch[1];
|
|
68
|
+
// export default Name
|
|
69
|
+
const simpleMatch = content.match(/export\s+default\s+(\w+)/);
|
|
70
|
+
if (simpleMatch)
|
|
71
|
+
return simpleMatch[1];
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
package/dist/scanner.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { defaultConfig } from "./config.js";
|
|
4
|
+
export async function scanDirectory(dir, options = {}) {
|
|
5
|
+
const config = { ...defaultConfig, ...options };
|
|
6
|
+
const files = [];
|
|
7
|
+
async function walk(currentDir, depth = 0) {
|
|
8
|
+
if (depth > config.maxDepth)
|
|
9
|
+
return;
|
|
10
|
+
let entries;
|
|
11
|
+
try {
|
|
12
|
+
entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
19
|
+
// Check ignore patterns
|
|
20
|
+
const shouldIgnore = config.ignore.some((pattern) => {
|
|
21
|
+
if (pattern.includes("*")) {
|
|
22
|
+
const regex = new RegExp(pattern.replace(/\./g, "\\.").replace(/\*/g, ".*"));
|
|
23
|
+
return regex.test(entry.name);
|
|
24
|
+
}
|
|
25
|
+
return entry.name === pattern;
|
|
26
|
+
});
|
|
27
|
+
if (shouldIgnore)
|
|
28
|
+
continue;
|
|
29
|
+
if (entry.isDirectory()) {
|
|
30
|
+
await walk(fullPath, depth + 1);
|
|
31
|
+
}
|
|
32
|
+
else if (entry.isFile()) {
|
|
33
|
+
const ext = path.extname(entry.name);
|
|
34
|
+
if (config.extensions.includes(ext)) {
|
|
35
|
+
files.push(fullPath);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
await walk(dir);
|
|
41
|
+
return files;
|
|
42
|
+
}
|
|
43
|
+
export async function getRoutes(appDir) {
|
|
44
|
+
const routes = [];
|
|
45
|
+
async function walkRoutes(dir, indent = "") {
|
|
46
|
+
let entries;
|
|
47
|
+
try {
|
|
48
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Sort: groups first (parentheses), then regular folders
|
|
54
|
+
const sorted = entries
|
|
55
|
+
.filter((e) => e.isDirectory())
|
|
56
|
+
.filter((e) => !e.name.startsWith("_"))
|
|
57
|
+
.sort((a, b) => {
|
|
58
|
+
const aIsGroup = a.name.startsWith("(");
|
|
59
|
+
const bIsGroup = b.name.startsWith("(");
|
|
60
|
+
if (aIsGroup && !bIsGroup)
|
|
61
|
+
return -1;
|
|
62
|
+
if (!aIsGroup && bIsGroup)
|
|
63
|
+
return 1;
|
|
64
|
+
return a.name.localeCompare(b.name);
|
|
65
|
+
});
|
|
66
|
+
for (const entry of sorted) {
|
|
67
|
+
routes.push(`${indent}${entry.name}`);
|
|
68
|
+
await walkRoutes(path.join(dir, entry.name), indent + " ");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
await walkRoutes(appDir);
|
|
72
|
+
return routes;
|
|
73
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface FileExports {
|
|
2
|
+
filePath: string;
|
|
3
|
+
fileName: string;
|
|
4
|
+
functions: string[];
|
|
5
|
+
constants: string[];
|
|
6
|
+
types: string[];
|
|
7
|
+
interfaces: string[];
|
|
8
|
+
classes: string[];
|
|
9
|
+
defaultExport?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ScanOptions {
|
|
12
|
+
entry: string;
|
|
13
|
+
extensions: string[];
|
|
14
|
+
ignore: string[];
|
|
15
|
+
maxDepth: number;
|
|
16
|
+
}
|
|
17
|
+
export interface ProjectContext {
|
|
18
|
+
routes: string[];
|
|
19
|
+
features: Map<string, FileExports[]>;
|
|
20
|
+
hooks: FileExports[];
|
|
21
|
+
lib: Map<string, FileExports[]>;
|
|
22
|
+
components: Map<string, FileExports[]>;
|
|
23
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@eloquence98/ctx",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Generate AI-ready context from your codebase. One command, zero config.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ctx": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsx src/cli.ts",
|
|
12
|
+
"test": "tsx src/cli.ts ."
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"ai",
|
|
16
|
+
"context",
|
|
17
|
+
"codebase",
|
|
18
|
+
"documentation",
|
|
19
|
+
"cli",
|
|
20
|
+
"typescript",
|
|
21
|
+
"nextjs",
|
|
22
|
+
"react"
|
|
23
|
+
],
|
|
24
|
+
"author": "Eloquence98",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/Eloquence98/ctx.git"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/Eloquence98/ctx",
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.0.0",
|
|
36
|
+
"tsx": "^4.0.0",
|
|
37
|
+
"typescript": "^5.0.0"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"commander": "^12.0.0",
|
|
41
|
+
"fast-glob": "^3.3.0"
|
|
42
|
+
}
|
|
43
|
+
}
|