Sign In

codefetch

Package Overview
Dependencies
Maintainers
1
Versions
47
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

codefetch - npm Package Compare versions

Comparing version
1.1.5
to
1.1.6
+206
-93
dist/index.cjs

@@ -8,2 +8,3 @@ #!/usr/bin/env node

const node_url = require('node:url');
const readline = require('readline');
const ignore = require('ignore');

@@ -17,2 +18,3 @@

const process__default = /*#__PURE__*/_interopDefaultCompat(process);
const readline__default = /*#__PURE__*/_interopDefaultCompat(readline);
const ignore__default = /*#__PURE__*/_interopDefaultCompat(ignore);

@@ -277,6 +279,11 @@

extensions: null,
verbose: false
verbose: false,
includeFiles: null,
excludeFiles: null,
includeDirs: null,
excludeDirs: null
};
for (let i = 2; i < argv.length; i++) {
const arg = argv[i];
const args = argv[0]?.endsWith("node") ? argv.slice(2) : argv;
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === "-h" || arg === "--help") {

@@ -287,7 +294,7 @@ printHelp();

result.verbose = true;
} else if ((arg === "-o" || arg === "--output") && argv[i + 1]) {
result.output = argv[i + 1];
} else if ((arg === "-o" || arg === "--output") && args[i + 1]) {
result.output = args[i + 1];
i++;
} else if ((arg === "--max-tokens" || arg === "-tok") && argv[i + 1]) {
const tokens = parseInt(argv[i + 1]);
} else if ((arg === "--max-tokens" || arg === "-tok") && args[i + 1]) {
const tokens = parseInt(args[i + 1]);
if (!isNaN(tokens)) {

@@ -297,5 +304,25 @@ result.maxTokens = tokens;

i++;
} else if ((arg === "-e" || arg === "--extension") && argv[i + 1]) {
result.extensions = argv[i + 1].split(",").map((ext) => ext.startsWith(".") ? ext : `.${ext}`);
} else if ((arg === "-e" || arg === "--extension") && args[i + 1]) {
result.extensions = args[i + 1].split(",").map((ext) => ext.startsWith(".") ? ext : `.${ext}`);
i++;
} else if ((arg === "-if" || arg === "--include-files") && args[i + 1]) {
result.includeFiles = args[i + 1].split(",");
i++;
} else if ((arg === "-ef" || arg === "--exclude-files") && args[i + 1]) {
result.excludeFiles = args[i + 1].split(",");
i++;
} else if ((arg === "-id" || arg === "--include-dir") && args[i + 1]) {
result.includeDirs = args[i + 1].split(",");
i++;
} else if ((arg === "-ed" || arg === "--exclude-dir") && args[i + 1]) {
result.excludeDirs = args[i + 1].split(",");
i++;
} else if (arg.startsWith("--include-files=")) {
result.includeFiles = arg.split("=")[1].split(",");
} else if (arg.startsWith("--exclude-files=")) {
result.excludeFiles = arg.split("=")[1].split(",");
} else if (arg.startsWith("--include-dir=")) {
result.includeDirs = arg.split("=")[1].split(",");
} else if (arg.startsWith("--exclude-dir=")) {
result.excludeDirs = arg.split("=")[1].split(",");
}

@@ -305,45 +332,119 @@ }

}
const { output, maxTokens, extensions, verbose } = parseArgs(process__default.argv);
const ig = ignore__default().add(
DEFAULT_IGNORE_PATTERNS.split("\n").filter(
(line) => line && !line.startsWith("#")
)
);
try {
const gitignoreContent = fs__default.readFileSync(
path__default.join(process__default.cwd(), ".gitignore"),
"utf8"
async function main() {
const {
output: outputFile,
// rename to avoid conflict
maxTokens,
extensions,
verbose,
includeFiles,
excludeFiles,
includeDirs,
excludeDirs
} = parseArgs(process__default.argv);
const ig = ignore__default().add(
DEFAULT_IGNORE_PATTERNS.split("\n").filter(
(line) => line && !line.startsWith("#")
)
);
ig.add(gitignoreContent);
} catch {
try {
const gitignoreContent = fs__default.readFileSync(
path__default.join(process__default.cwd(), ".gitignore"),
"utf8"
);
ig.add(gitignoreContent);
} catch {
}
try {
const codefetchignoreContent = fs__default.readFileSync(
path__default.join(process__default.cwd(), ".codefetchignore"),
"utf8"
);
ig.add(codefetchignoreContent);
} catch {
}
const extensionSet = extensions ? new Set(extensions) : null;
const allFiles = await collectFiles(process__default.cwd(), {
ig,
extensionSet,
excludeFiles,
includeFiles,
excludeDirs,
includeDirs
});
if (outputFile) {
const codefetchDir = path__default.join(process__default.cwd(), "codefetch");
if (!fs__default.existsSync(codefetchDir)) {
fs__default.mkdirSync(codefetchDir, { recursive: true });
console.log("Created codefetch directory.");
}
const codefetchignorePath = path__default.join(process__default.cwd(), ".codefetchignore");
if (!fs__default.existsSync(codefetchignorePath)) {
const ignoreContent = "# Codefetch specific ignores\ncodefetch/\n";
fs__default.writeFileSync(codefetchignorePath, ignoreContent, "utf8");
console.log(
"Created .codefetchignore file. Add 'codefetch/' to your .gitignore to avoid committing fetched code."
);
}
}
const outputPath = outputFile ? path__default.join(process__default.cwd(), "codefetch", outputFile) : null;
const totalTokens = await generateMarkdown(allFiles, {
outputPath,
maxTokens,
verbose
});
if (outputFile) {
console.log("\nSummary:");
console.log("\u2713 Code was successfully fetched");
console.log(`\u2713 Output written to: ${outputPath}`);
console.log(`\u2713 Approximate token count: ${totalTokens}`);
}
}
try {
const codefetchignoreContent = fs__default.readFileSync(
path__default.join(process__default.cwd(), ".codefetchignore"),
"utf8"
async function collectFiles(dir, options) {
const results = [];
const list = await fs__default.promises.readdir(dir);
const excludePatterns = options.excludeFiles?.map(
(pattern) => new RegExp(pattern.replace(/\*/g, ".*"))
);
ig.add(codefetchignoreContent);
} catch {
}
function collectFiles(dir) {
const results = [];
const list = fs__default.readdirSync(dir);
const includePatterns = options.includeFiles?.map(
(pattern) => new RegExp(pattern.replace(/\*/g, ".*"))
);
for (const filename of list) {
const filePath = path__default.join(dir, filename);
const relPath = path__default.relative(process__default.cwd(), filePath);
if (ig.ignores(relPath)) {
if (options.ig.ignores(relPath)) {
continue;
}
const stat = fs__default.statSync(filePath);
const stat = await fs__default.promises.stat(filePath);
if (stat.isDirectory()) {
verbose && console.log(`Processing directory: ${relPath}`);
results.push(...collectFiles(filePath));
const dirName = path__default.basename(filePath);
if (options.excludeDirs && options.excludeDirs.includes(dirName)) {
continue;
}
if (options.includeDirs && !options.includeDirs.includes(dirName)) {
continue;
}
results.push(
...await collectFiles(filePath, {
ig: options.ig,
extensionSet: options.extensionSet,
excludeFiles: options.excludeFiles,
includeFiles: options.includeFiles,
excludeDirs: options.excludeDirs,
includeDirs: options.includeDirs
})
);
} else {
if (extensions) {
if (excludePatterns && excludePatterns.some((pattern) => pattern.test(filename))) {
continue;
}
if (includePatterns && !includePatterns.some((pattern) => pattern.test(filename))) {
continue;
}
if (options.extensionSet) {
const ext = path__default.extname(filename);
if (!extensions.includes(ext)) {
if (!options.extensionSet.has(ext)) {
continue;
}
}
verbose && console.log(`Processing file: ${relPath}`);
results.push(filePath);

@@ -354,59 +455,52 @@ }

}
const allFiles = collectFiles(process__default.cwd());
function estimateTokens(text) {
return text.split(/[\s\p{P}]+/u).length;
}
function generateMarkdown(files) {
const lines = [];
async function generateMarkdown(files, options) {
if (options.outputPath) {
const outputDir = path__default.dirname(options.outputPath);
if (!fs__default.existsSync(outputDir)) {
fs__default.mkdirSync(outputDir, { recursive: true });
}
}
const output = options.outputPath ? fs__default.createWriteStream(options.outputPath) : process__default.stdout;
let totalTokens = 0;
verbose && console.log("\nGenerating markdown output...");
for (const file of files) {
const relativePath = path__default.relative(process__default.cwd(), file);
const content = fs__default.readFileSync(file, "utf8");
const fileTokens = estimateTokens(content);
if (maxTokens && totalTokens + fileTokens > maxTokens) {
verbose && console.log(`Skipping ${relativePath} (would exceed token limit)`);
continue;
const fileStream = fs__default.createReadStream(file, { encoding: "utf8" });
const rl = readline__default.createInterface({
input: fileStream,
crlfDelay: Infinity
});
output.write(`/${relativePath}:
`);
output.write("-".repeat(80) + "\n");
let lineNumber = 1;
for await (const line of rl) {
const lineTokens = estimateTokens(line);
if (options.maxTokens && totalTokens + lineTokens > options.maxTokens) {
break;
}
const formattedLine = `${lineNumber} | ${line}
`;
output.write(formattedLine);
totalTokens += lineTokens;
lineNumber++;
}
verbose && console.log(`Adding to output: ${relativePath} (${fileTokens} tokens)`);
totalTokens += fileTokens;
lines.push(`/${relativePath}:`);
lines.push(
"--------------------------------------------------------------------------------"
);
const fileLines = content.split("\n");
fileLines.forEach((line, i) => {
lines.push(`${i + 1} | ${line}`);
output.write("-".repeat(80) + "\n\n");
if (options.maxTokens && totalTokens >= options.maxTokens) {
break;
}
}
if (options.outputPath) {
await new Promise((resolve, reject) => {
output.end((err) => {
if (err)
reject(err);
else
resolve();
});
});
lines.push("");
lines.push(
"--------------------------------------------------------------------------------"
);
}
return lines.join("\n");
return totalTokens;
}
const final = generateMarkdown(allFiles);
if (output) {
const codefetchDir = path__default.join(process__default.cwd(), "codefetch");
if (!fs__default.existsSync(codefetchDir)) {
fs__default.mkdirSync(codefetchDir, { recursive: true });
console.log("Created codefetch directory.");
}
const codefetchignorePath = path__default.join(process__default.cwd(), ".codefetchignore");
if (!fs__default.existsSync(codefetchignorePath)) {
const ignoreContent = "# Codefetch specific ignores\ncodefetch/\n";
fs__default.writeFileSync(codefetchignorePath, ignoreContent, "utf8");
console.log(
"Created .codefetchignore file. Add 'codefetch/' to your .gitignore to avoid committing fetched code."
);
}
const outputPath = path__default.join(codefetchDir, output);
fs__default.writeFileSync(outputPath, final, "utf8");
const totalTokens = estimateTokens(final);
console.log("\nSummary:");
console.log("\u2713 Code was successfully fetched");
console.log(`\u2713 Output written to: ${outputPath}`);
console.log(`\u2713 Approximate token count: ${totalTokens}`);
} else {
console.log(final);
function estimateTokens(text) {
return text.split(/[\s\p{P}]+/u).filter(Boolean).length;
}

@@ -418,8 +512,27 @@ function printHelp() {

Options:
-o, --output <file> Specify output filename
-tok, --max-tokens <n> Limit output tokens (useful for AI models)
-e, --extension <ext,...> Filter by file extensions (e.g., .ts,.js)
-v, --verbose Show detailed processing information
-h, --help Display this help message
-o, --output <file> Specify output filename
-tok, --max-tokens <n> Limit output tokens (useful for AI models)
-e, --extension <ext,...> Filter by file extensions (e.g., .ts,.js)
-if, --include-files <p,..> Include specific files (supports patterns)
-ef, --exclude-files <p,..> Exclude specific files (supports patterns)
-id, --include-dir <d,...> Include specific directories
-ed, --exclude-dir <d,...> Exclude specific directories
-v, --verbose Show detailed processing information
-h, --help Display this help message
Examples:
codefetch --exclude-dir=node_modules,public
codefetch --include-files=*.ts,*.js
codefetch -ef test.ts,temp.js -id src
`);
}
if (require.main === module) {
main().catch((error) => {
console.error("Error:", error);
process__default.exit(1);
});
}
exports.collectFiles = collectFiles;
exports.generateMarkdown = generateMarkdown;
exports.parseArgs = parseArgs;

@@ -0,2 +1,39 @@

import ignore from 'ignore';
export { }
interface ParsedArgs {
output: string | null;
maxTokens: number | null;
extensions: string[] | null;
verbose: boolean;
includeFiles: string[] | null;
excludeFiles: string[] | null;
includeDirs: string[] | null;
excludeDirs: string[] | null;
}
/**
* Simple function to parse CLI args:
*
* -o, --output <file> : specify output filename
* --max-tokens, -tok <number> : limit output tokens
* -e, --extension <ext,...> : filter by file extensions (.ts,.js etc)
* -if, --include-files <pattern,...> : include specific files
* -ef, --exclude-files <pattern,...> : exclude specific files
* -id, --include-dir <pattern,...> : include specific directories
* -ed, --exclude-dir <pattern,...> : exclude specific directories
*/
declare function parseArgs(argv: string[]): ParsedArgs;
declare function collectFiles(dir: string, options: {
ig: ReturnType<typeof ignore>;
extensionSet: Set<string> | null;
excludeFiles: string[] | null;
includeFiles: string[] | null;
excludeDirs: string[] | null;
includeDirs: string[] | null;
}): Promise<string[]>;
declare function generateMarkdown(files: string[], options: {
outputPath: string | null;
maxTokens: number | null;
verbose: boolean;
}): Promise<number>;
export { type ParsedArgs, collectFiles, generateMarkdown, parseArgs };

@@ -0,2 +1,39 @@

import ignore from 'ignore';
export { }
interface ParsedArgs {
output: string | null;
maxTokens: number | null;
extensions: string[] | null;
verbose: boolean;
includeFiles: string[] | null;
excludeFiles: string[] | null;
includeDirs: string[] | null;
excludeDirs: string[] | null;
}
/**
* Simple function to parse CLI args:
*
* -o, --output <file> : specify output filename
* --max-tokens, -tok <number> : limit output tokens
* -e, --extension <ext,...> : filter by file extensions (.ts,.js etc)
* -if, --include-files <pattern,...> : include specific files
* -ef, --exclude-files <pattern,...> : exclude specific files
* -id, --include-dir <pattern,...> : include specific directories
* -ed, --exclude-dir <pattern,...> : exclude specific directories
*/
declare function parseArgs(argv: string[]): ParsedArgs;
declare function collectFiles(dir: string, options: {
ig: ReturnType<typeof ignore>;
extensionSet: Set<string> | null;
excludeFiles: string[] | null;
includeFiles: string[] | null;
excludeDirs: string[] | null;
includeDirs: string[] | null;
}): Promise<string[]>;
declare function generateMarkdown(files: string[], options: {
outputPath: string | null;
maxTokens: number | null;
verbose: boolean;
}): Promise<number>;
export { type ParsedArgs, collectFiles, generateMarkdown, parseArgs };

@@ -0,2 +1,39 @@

import ignore from 'ignore';
export { }
interface ParsedArgs {
output: string | null;
maxTokens: number | null;
extensions: string[] | null;
verbose: boolean;
includeFiles: string[] | null;
excludeFiles: string[] | null;
includeDirs: string[] | null;
excludeDirs: string[] | null;
}
/**
* Simple function to parse CLI args:
*
* -o, --output <file> : specify output filename
* --max-tokens, -tok <number> : limit output tokens
* -e, --extension <ext,...> : filter by file extensions (.ts,.js etc)
* -if, --include-files <pattern,...> : include specific files
* -ef, --exclude-files <pattern,...> : exclude specific files
* -id, --include-dir <pattern,...> : include specific directories
* -ed, --exclude-dir <pattern,...> : exclude specific directories
*/
declare function parseArgs(argv: string[]): ParsedArgs;
declare function collectFiles(dir: string, options: {
ig: ReturnType<typeof ignore>;
extensionSet: Set<string> | null;
excludeFiles: string[] | null;
includeFiles: string[] | null;
excludeDirs: string[] | null;
includeDirs: string[] | null;
}): Promise<string[]>;
declare function generateMarkdown(files: string[], options: {
outputPath: string | null;
maxTokens: number | null;
verbose: boolean;
}): Promise<number>;
export { type ParsedArgs, collectFiles, generateMarkdown, parseArgs };

@@ -6,2 +6,3 @@ #!/usr/bin/env node

import { fileURLToPath } from 'node:url';
import readline from 'readline';
import ignore from 'ignore';

@@ -266,6 +267,11 @@

extensions: null,
verbose: false
verbose: false,
includeFiles: null,
excludeFiles: null,
includeDirs: null,
excludeDirs: null
};
for (let i = 2; i < argv.length; i++) {
const arg = argv[i];
const args = argv[0]?.endsWith("node") ? argv.slice(2) : argv;
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === "-h" || arg === "--help") {

@@ -276,7 +282,7 @@ printHelp();

result.verbose = true;
} else if ((arg === "-o" || arg === "--output") && argv[i + 1]) {
result.output = argv[i + 1];
} else if ((arg === "-o" || arg === "--output") && args[i + 1]) {
result.output = args[i + 1];
i++;
} else if ((arg === "--max-tokens" || arg === "-tok") && argv[i + 1]) {
const tokens = parseInt(argv[i + 1]);
} else if ((arg === "--max-tokens" || arg === "-tok") && args[i + 1]) {
const tokens = parseInt(args[i + 1]);
if (!isNaN(tokens)) {

@@ -286,5 +292,25 @@ result.maxTokens = tokens;

i++;
} else if ((arg === "-e" || arg === "--extension") && argv[i + 1]) {
result.extensions = argv[i + 1].split(",").map((ext) => ext.startsWith(".") ? ext : `.${ext}`);
} else if ((arg === "-e" || arg === "--extension") && args[i + 1]) {
result.extensions = args[i + 1].split(",").map((ext) => ext.startsWith(".") ? ext : `.${ext}`);
i++;
} else if ((arg === "-if" || arg === "--include-files") && args[i + 1]) {
result.includeFiles = args[i + 1].split(",");
i++;
} else if ((arg === "-ef" || arg === "--exclude-files") && args[i + 1]) {
result.excludeFiles = args[i + 1].split(",");
i++;
} else if ((arg === "-id" || arg === "--include-dir") && args[i + 1]) {
result.includeDirs = args[i + 1].split(",");
i++;
} else if ((arg === "-ed" || arg === "--exclude-dir") && args[i + 1]) {
result.excludeDirs = args[i + 1].split(",");
i++;
} else if (arg.startsWith("--include-files=")) {
result.includeFiles = arg.split("=")[1].split(",");
} else if (arg.startsWith("--exclude-files=")) {
result.excludeFiles = arg.split("=")[1].split(",");
} else if (arg.startsWith("--include-dir=")) {
result.includeDirs = arg.split("=")[1].split(",");
} else if (arg.startsWith("--exclude-dir=")) {
result.excludeDirs = arg.split("=")[1].split(",");
}

@@ -294,45 +320,119 @@ }

}
const { output, maxTokens, extensions, verbose } = parseArgs(process.argv);
const ig = ignore().add(
DEFAULT_IGNORE_PATTERNS.split("\n").filter(
(line) => line && !line.startsWith("#")
)
);
try {
const gitignoreContent = fs.readFileSync(
path.join(process.cwd(), ".gitignore"),
"utf8"
async function main() {
const {
output: outputFile,
// rename to avoid conflict
maxTokens,
extensions,
verbose,
includeFiles,
excludeFiles,
includeDirs,
excludeDirs
} = parseArgs(process.argv);
const ig = ignore().add(
DEFAULT_IGNORE_PATTERNS.split("\n").filter(
(line) => line && !line.startsWith("#")
)
);
ig.add(gitignoreContent);
} catch {
try {
const gitignoreContent = fs.readFileSync(
path.join(process.cwd(), ".gitignore"),
"utf8"
);
ig.add(gitignoreContent);
} catch {
}
try {
const codefetchignoreContent = fs.readFileSync(
path.join(process.cwd(), ".codefetchignore"),
"utf8"
);
ig.add(codefetchignoreContent);
} catch {
}
const extensionSet = extensions ? new Set(extensions) : null;
const allFiles = await collectFiles(process.cwd(), {
ig,
extensionSet,
excludeFiles,
includeFiles,
excludeDirs,
includeDirs
});
if (outputFile) {
const codefetchDir = path.join(process.cwd(), "codefetch");
if (!fs.existsSync(codefetchDir)) {
fs.mkdirSync(codefetchDir, { recursive: true });
console.log("Created codefetch directory.");
}
const codefetchignorePath = path.join(process.cwd(), ".codefetchignore");
if (!fs.existsSync(codefetchignorePath)) {
const ignoreContent = "# Codefetch specific ignores\ncodefetch/\n";
fs.writeFileSync(codefetchignorePath, ignoreContent, "utf8");
console.log(
"Created .codefetchignore file. Add 'codefetch/' to your .gitignore to avoid committing fetched code."
);
}
}
const outputPath = outputFile ? path.join(process.cwd(), "codefetch", outputFile) : null;
const totalTokens = await generateMarkdown(allFiles, {
outputPath,
maxTokens,
verbose
});
if (outputFile) {
console.log("\nSummary:");
console.log("\u2713 Code was successfully fetched");
console.log(`\u2713 Output written to: ${outputPath}`);
console.log(`\u2713 Approximate token count: ${totalTokens}`);
}
}
try {
const codefetchignoreContent = fs.readFileSync(
path.join(process.cwd(), ".codefetchignore"),
"utf8"
async function collectFiles(dir, options) {
const results = [];
const list = await fs.promises.readdir(dir);
const excludePatterns = options.excludeFiles?.map(
(pattern) => new RegExp(pattern.replace(/\*/g, ".*"))
);
ig.add(codefetchignoreContent);
} catch {
}
function collectFiles(dir) {
const results = [];
const list = fs.readdirSync(dir);
const includePatterns = options.includeFiles?.map(
(pattern) => new RegExp(pattern.replace(/\*/g, ".*"))
);
for (const filename of list) {
const filePath = path.join(dir, filename);
const relPath = path.relative(process.cwd(), filePath);
if (ig.ignores(relPath)) {
if (options.ig.ignores(relPath)) {
continue;
}
const stat = fs.statSync(filePath);
const stat = await fs.promises.stat(filePath);
if (stat.isDirectory()) {
verbose && console.log(`Processing directory: ${relPath}`);
results.push(...collectFiles(filePath));
const dirName = path.basename(filePath);
if (options.excludeDirs && options.excludeDirs.includes(dirName)) {
continue;
}
if (options.includeDirs && !options.includeDirs.includes(dirName)) {
continue;
}
results.push(
...await collectFiles(filePath, {
ig: options.ig,
extensionSet: options.extensionSet,
excludeFiles: options.excludeFiles,
includeFiles: options.includeFiles,
excludeDirs: options.excludeDirs,
includeDirs: options.includeDirs
})
);
} else {
if (extensions) {
if (excludePatterns && excludePatterns.some((pattern) => pattern.test(filename))) {
continue;
}
if (includePatterns && !includePatterns.some((pattern) => pattern.test(filename))) {
continue;
}
if (options.extensionSet) {
const ext = path.extname(filename);
if (!extensions.includes(ext)) {
if (!options.extensionSet.has(ext)) {
continue;
}
}
verbose && console.log(`Processing file: ${relPath}`);
results.push(filePath);

@@ -343,59 +443,52 @@ }

}
const allFiles = collectFiles(process.cwd());
function estimateTokens(text) {
return text.split(/[\s\p{P}]+/u).length;
}
function generateMarkdown(files) {
const lines = [];
async function generateMarkdown(files, options) {
if (options.outputPath) {
const outputDir = path.dirname(options.outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
}
const output = options.outputPath ? fs.createWriteStream(options.outputPath) : process.stdout;
let totalTokens = 0;
verbose && console.log("\nGenerating markdown output...");
for (const file of files) {
const relativePath = path.relative(process.cwd(), file);
const content = fs.readFileSync(file, "utf8");
const fileTokens = estimateTokens(content);
if (maxTokens && totalTokens + fileTokens > maxTokens) {
verbose && console.log(`Skipping ${relativePath} (would exceed token limit)`);
continue;
const fileStream = fs.createReadStream(file, { encoding: "utf8" });
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
output.write(`/${relativePath}:
`);
output.write("-".repeat(80) + "\n");
let lineNumber = 1;
for await (const line of rl) {
const lineTokens = estimateTokens(line);
if (options.maxTokens && totalTokens + lineTokens > options.maxTokens) {
break;
}
const formattedLine = `${lineNumber} | ${line}
`;
output.write(formattedLine);
totalTokens += lineTokens;
lineNumber++;
}
verbose && console.log(`Adding to output: ${relativePath} (${fileTokens} tokens)`);
totalTokens += fileTokens;
lines.push(`/${relativePath}:`);
lines.push(
"--------------------------------------------------------------------------------"
);
const fileLines = content.split("\n");
fileLines.forEach((line, i) => {
lines.push(`${i + 1} | ${line}`);
output.write("-".repeat(80) + "\n\n");
if (options.maxTokens && totalTokens >= options.maxTokens) {
break;
}
}
if (options.outputPath) {
await new Promise((resolve, reject) => {
output.end((err) => {
if (err)
reject(err);
else
resolve();
});
});
lines.push("");
lines.push(
"--------------------------------------------------------------------------------"
);
}
return lines.join("\n");
return totalTokens;
}
const final = generateMarkdown(allFiles);
if (output) {
const codefetchDir = path.join(process.cwd(), "codefetch");
if (!fs.existsSync(codefetchDir)) {
fs.mkdirSync(codefetchDir, { recursive: true });
console.log("Created codefetch directory.");
}
const codefetchignorePath = path.join(process.cwd(), ".codefetchignore");
if (!fs.existsSync(codefetchignorePath)) {
const ignoreContent = "# Codefetch specific ignores\ncodefetch/\n";
fs.writeFileSync(codefetchignorePath, ignoreContent, "utf8");
console.log(
"Created .codefetchignore file. Add 'codefetch/' to your .gitignore to avoid committing fetched code."
);
}
const outputPath = path.join(codefetchDir, output);
fs.writeFileSync(outputPath, final, "utf8");
const totalTokens = estimateTokens(final);
console.log("\nSummary:");
console.log("\u2713 Code was successfully fetched");
console.log(`\u2713 Output written to: ${outputPath}`);
console.log(`\u2713 Approximate token count: ${totalTokens}`);
} else {
console.log(final);
function estimateTokens(text) {
return text.split(/[\s\p{P}]+/u).filter(Boolean).length;
}

@@ -407,8 +500,25 @@ function printHelp() {

Options:
-o, --output <file> Specify output filename
-tok, --max-tokens <n> Limit output tokens (useful for AI models)
-e, --extension <ext,...> Filter by file extensions (e.g., .ts,.js)
-v, --verbose Show detailed processing information
-h, --help Display this help message
-o, --output <file> Specify output filename
-tok, --max-tokens <n> Limit output tokens (useful for AI models)
-e, --extension <ext,...> Filter by file extensions (e.g., .ts,.js)
-if, --include-files <p,..> Include specific files (supports patterns)
-ef, --exclude-files <p,..> Exclude specific files (supports patterns)
-id, --include-dir <d,...> Include specific directories
-ed, --exclude-dir <d,...> Exclude specific directories
-v, --verbose Show detailed processing information
-h, --help Display this help message
Examples:
codefetch --exclude-dir=node_modules,public
codefetch --include-files=*.ts,*.js
codefetch -ef test.ts,temp.js -id src
`);
}
if (require.main === module) {
main().catch((error) => {
console.error("Error:", error);
process.exit(1);
});
}
export { collectFiles, generateMarkdown, parseArgs };
{
"name": "codefetch",
"version": "1.1.5",
"version": "1.1.6",
"description": "Fetches all files in the current directory and outputs them in a Markdown file.",

@@ -21,3 +21,6 @@ "type": "module",

"dev": "unbuild --stub",
"prepack": "unbuild"
"prepack": "unbuild",
"test": "vitest run --coverage",
"test:watch": "vitest",
"test:types": "tsc --noEmit --skipLibCheck"
},

@@ -33,3 +36,5 @@ "files": [

"typescript": "^5.0.0",
"@types/node": "^20.0.0"
"@types/node": "^20.0.0",
"vitest": "^1.1.3",
"@vitest/coverage-v8": "^1.1.3"
},

@@ -36,0 +41,0 @@ "main": "./dist/index.cjs",

@@ -28,2 +28,14 @@ # codefetch

Include or exclude specific files and directories:
```bash
# Exclude node_modules and public directories
npx codefetch --exclude-dir=node_modules,public -o output.md
# Include only TypeScript files
npx codefetch --include-files=*.ts -o typescript-only.md
# Include src directory, exclude test files
npx codefetch --include-dir=src --exclude-files=*.test.ts -o src-no-tests.md
```
If no output file is specified (`-o` or `--output`), it will print to stdout.

@@ -38,4 +50,10 @@

| `--extension <ext,...>` | `-e` | Filter by file extensions (e.g., .ts,.js) |
| `--include-files <pattern,...>` | `-if` | Include specific files (supports patterns like *.ts) |
| `--exclude-files <pattern,...>` | `-ef` | Exclude specific files (supports patterns like *.test.ts) |
| `--include-dir <dir,...>` | `-id` | Include specific directories |
| `--exclude-dir <dir,...>` | `-ed` | Exclude specific directories |
| `--verbose` | `-v` | Show detailed processing information |
All options that accept multiple values use comma-separated lists. File patterns support wildcards (e.g., `*.ts`, `src/*.js`).
## Installation

@@ -42,0 +60,0 @@