@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 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.5",
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
+ }