@dev-angsu/cli 1.0.5 ā 1.0.6
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/bin/index.js +12 -0
- package/package.json +5 -1
- package/src/commands/copy.js +87 -0
- package/src/utils/file-reader.js +58 -0
package/bin/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import ora from "ora";
|
|
|
11
11
|
import fs from "fs";
|
|
12
12
|
|
|
13
13
|
import { run as review } from "../src/utils/engine.js";
|
|
14
|
+
import { copyCode } from "../src/commands/copy.js";
|
|
14
15
|
|
|
15
16
|
const program = new Command();
|
|
16
17
|
|
|
@@ -84,5 +85,16 @@ program
|
|
|
84
85
|
await review();
|
|
85
86
|
});
|
|
86
87
|
|
|
88
|
+
// 4. Copy Code Command
|
|
89
|
+
program
|
|
90
|
+
.command("copycode")
|
|
91
|
+
.alias("cp")
|
|
92
|
+
.description("Copy codebase context to clipboard for LLMs")
|
|
93
|
+
.option("-o, --output <file>", "Output result to a file instead of clipboard")
|
|
94
|
+
.option("-d, --dry-run", "Dry run: only list files and stats")
|
|
95
|
+
.action(async (options) => {
|
|
96
|
+
await copyCode(process.cwd(), options);
|
|
97
|
+
});
|
|
98
|
+
|
|
87
99
|
// 6. Parse Arguments
|
|
88
100
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dev-angsu/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -18,9 +18,13 @@
|
|
|
18
18
|
"@actions/core": "^2.0.1",
|
|
19
19
|
"@actions/github": "^6.0.1",
|
|
20
20
|
"chalk": "^5.6.2",
|
|
21
|
+
"clipboardy": "^4.0.0",
|
|
21
22
|
"commander": "^14.0.2",
|
|
22
23
|
"dotenv": "^17.2.3",
|
|
24
|
+
"fast-glob": "^3.3.3",
|
|
25
|
+
"ignore": "^5.3.2",
|
|
23
26
|
"inquirer": "^13.1.0",
|
|
27
|
+
"isbinaryfile": "^5.0.7",
|
|
24
28
|
"openai": "^6.15.0",
|
|
25
29
|
"ora": "^9.0.0",
|
|
26
30
|
"simple-git": "^3.30.0"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { getValidFiles } from "../utils/file-reader.js";
|
|
2
|
+
import clipboardy from "clipboardy";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import ora from "ora";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import inquirer from "inquirer";
|
|
8
|
+
|
|
9
|
+
export async function copyCode(dir = process.cwd(), options = {}) {
|
|
10
|
+
const spinner = ora("Scanning directory...").start();
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const files = await getValidFiles(dir);
|
|
14
|
+
|
|
15
|
+
if (files.length === 0) {
|
|
16
|
+
spinner.fail("No valid files found to copy.");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
spinner.text = `Processing ${files.length} files...`;
|
|
21
|
+
|
|
22
|
+
let output = "";
|
|
23
|
+
let tokenCount = 0;
|
|
24
|
+
|
|
25
|
+
// Generate Tree Structure for context
|
|
26
|
+
const tree = files.join("\n");
|
|
27
|
+
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
const content = fs.readFileSync(path.join(dir, file), "utf-8");
|
|
30
|
+
// Wrap in Markdown code blocks with filename
|
|
31
|
+
if (!options.dryRun) {
|
|
32
|
+
output += `\n\n# File: ${file}\n\`\`\`\n${content}\n\`\`\``;
|
|
33
|
+
}
|
|
34
|
+
tokenCount += content.length / 4; // Rough estimate (1 token ~= 4 chars)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (options.dryRun) {
|
|
38
|
+
spinner.succeed(chalk.green("Dry run complete."));
|
|
39
|
+
console.log(chalk.bold("\nFile Structure:"));
|
|
40
|
+
console.log(tree);
|
|
41
|
+
} else {
|
|
42
|
+
// Final Payload
|
|
43
|
+
const finalOutput = `# Project Context\n\n## File Structure\n\`\`\`\n${tree}\n\`\`\`\n${output}`;
|
|
44
|
+
|
|
45
|
+
if (options.output) {
|
|
46
|
+
fs.writeFileSync(path.join(dir, options.output), finalOutput);
|
|
47
|
+
spinner.succeed(chalk.green(`Context saved to ${options.output} š`));
|
|
48
|
+
} else {
|
|
49
|
+
const LARGE_FILE_LIMIT = 500000; // ~500KB
|
|
50
|
+
if (finalOutput.length > LARGE_FILE_LIMIT) {
|
|
51
|
+
spinner.stop();
|
|
52
|
+
console.log(
|
|
53
|
+
chalk.yellow(
|
|
54
|
+
`\nā ļø Warning: Output is large (${Math.round(
|
|
55
|
+
finalOutput.length / 1024
|
|
56
|
+
)}KB).`
|
|
57
|
+
)
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const { proceed } = await inquirer.prompt([
|
|
61
|
+
{
|
|
62
|
+
type: "confirm",
|
|
63
|
+
name: "proceed",
|
|
64
|
+
message: "Copying this might freeze your clipboard. Proceed?",
|
|
65
|
+
default: false,
|
|
66
|
+
},
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
if (!proceed)
|
|
70
|
+
return console.log(
|
|
71
|
+
chalk.blue("Aborted. Try using --output <file> instead.")
|
|
72
|
+
);
|
|
73
|
+
spinner.start("Copying to clipboard...");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await clipboardy.write(finalOutput);
|
|
77
|
+
spinner.succeed(chalk.green("Context copied to clipboard! š"));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
console.log(chalk.dim(`\nStats:`));
|
|
81
|
+
console.log(chalk.dim(`- Files: ${files.length}`));
|
|
82
|
+
console.log(chalk.dim(`- Est. Tokens: ~${Math.round(tokenCount)}`));
|
|
83
|
+
} catch (error) {
|
|
84
|
+
spinner.fail("Failed to copy context.");
|
|
85
|
+
console.error(error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import glob from "fast-glob";
|
|
2
|
+
import ignore from "ignore";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { isBinaryFile } from "isbinaryfile";
|
|
6
|
+
|
|
7
|
+
export async function getValidFiles(dir) {
|
|
8
|
+
const ig = ignore();
|
|
9
|
+
|
|
10
|
+
// 1. Default Ignores (Standard noise)
|
|
11
|
+
ig.add([
|
|
12
|
+
".git",
|
|
13
|
+
"node_modules",
|
|
14
|
+
"dist",
|
|
15
|
+
"build",
|
|
16
|
+
"coverage",
|
|
17
|
+
".DS_Store",
|
|
18
|
+
"package-lock.json",
|
|
19
|
+
"yarn.lock",
|
|
20
|
+
"pnpm-lock.yaml",
|
|
21
|
+
".env",
|
|
22
|
+
".env.local",
|
|
23
|
+
"*.png",
|
|
24
|
+
"*.jpg",
|
|
25
|
+
"*.jpeg",
|
|
26
|
+
"*.gif",
|
|
27
|
+
"*.ico",
|
|
28
|
+
"*.svg", // Images
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
// 2. Load .gitignore if exists
|
|
32
|
+
const gitignorePath = path.join(dir, ".gitignore");
|
|
33
|
+
if (fs.existsSync(gitignorePath)) {
|
|
34
|
+
const gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
|
|
35
|
+
ig.add(gitignoreContent);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 3. Scan Files (Fast Glob)
|
|
39
|
+
const allFiles = await glob("**/*", {
|
|
40
|
+
cwd: dir,
|
|
41
|
+
dot: true,
|
|
42
|
+
ignore: ["**/node_modules/**", "**/.git/**"], // Optimization: Hard ignore
|
|
43
|
+
onlyFiles: true,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// 4. Filter Results
|
|
47
|
+
const validFiles = [];
|
|
48
|
+
for (const file of allFiles) {
|
|
49
|
+
// Check .gitignore rules
|
|
50
|
+
if (ig.ignores(file)) continue;
|
|
51
|
+
|
|
52
|
+
// Check if Binary (prevents garbage text)
|
|
53
|
+
const isBinary = await isBinaryFile(path.join(dir, file));
|
|
54
|
+
if (!isBinary) validFiles.push(file);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return validFiles;
|
|
58
|
+
}
|