contextforge-cli-harshil 1.0.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/src/scan.js ADDED
@@ -0,0 +1,206 @@
1
+ /**
2
+ * scan.js
3
+ * Scans the repository using fast-glob and returns a list of relevant files.
4
+ */
5
+
6
+ import fg from 'fast-glob';
7
+ import { readFileSync, existsSync } from 'fs';
8
+ import { resolve, relative } from 'path';
9
+ import ignore from 'ignore';
10
+
11
+ // Patterns to always include
12
+ const INCLUDE_PATTERNS = [
13
+ 'src/**',
14
+ 'lib/**',
15
+ 'app/**',
16
+ 'pages/**',
17
+ 'components/**',
18
+ 'routes/**',
19
+ 'controllers/**',
20
+ 'services/**',
21
+ 'middleware/**',
22
+ 'middlewares/**',
23
+ 'models/**',
24
+ 'schemas/**',
25
+ 'prisma/**',
26
+ 'config/**',
27
+ 'configs/**',
28
+ 'utils/**',
29
+ 'helpers/**',
30
+ 'hooks/**',
31
+ 'store/**',
32
+ 'api/**',
33
+ 'docs/**',
34
+ 'scripts/**',
35
+ 'README.md',
36
+ 'readme.md',
37
+ 'package.json',
38
+ '.env.example',
39
+ '.env.sample',
40
+ 'docker-compose.yml',
41
+ 'docker-compose.yaml',
42
+ 'Dockerfile',
43
+ 'tsconfig.json',
44
+ 'jsconfig.json',
45
+ 'vite.config.*',
46
+ 'next.config.*',
47
+ 'webpack.config.*',
48
+ '.eslintrc*',
49
+ 'babel.config.*',
50
+ 'jest.config.*',
51
+ 'tailwind.config.*',
52
+ 'index.js',
53
+ 'index.ts',
54
+ 'main.js',
55
+ 'main.ts',
56
+ 'app.js',
57
+ 'app.ts',
58
+ 'server.js',
59
+ 'server.ts',
60
+ ];
61
+
62
+ // Patterns to always ignore
63
+ const IGNORE_PATTERNS = [
64
+ 'node_modules/**',
65
+ 'dist/**',
66
+ 'build/**',
67
+ '.git/**',
68
+ 'coverage/**',
69
+ '.next/**',
70
+ '.nuxt/**',
71
+ '.cache/**',
72
+ '.turbo/**',
73
+ 'out/**',
74
+ '**/*.log',
75
+ '**/*.lock',
76
+ '**/*.min.js',
77
+ '**/*.min.css',
78
+ '**/*.map',
79
+ '**/*.snap',
80
+ '**/*.png',
81
+ '**/*.jpg',
82
+ '**/*.jpeg',
83
+ '**/*.gif',
84
+ '**/*.svg',
85
+ '**/*.ico',
86
+ '**/*.woff',
87
+ '**/*.woff2',
88
+ '**/*.ttf',
89
+ '**/*.eot',
90
+ '**/*.mp4',
91
+ '**/*.mp3',
92
+ '**/*.pdf',
93
+ '**/*.zip',
94
+ '**/*.tar.gz',
95
+ 'context.md',
96
+ '.env',
97
+ ];
98
+
99
+ /**
100
+ * Reads .gitignore if present and returns an `ignore` instance.
101
+ * @param {string} cwd
102
+ */
103
+ function loadGitignore(cwd) {
104
+ const gitignorePath = resolve(cwd, '.gitignore');
105
+ const ig = ignore();
106
+ if (existsSync(gitignorePath)) {
107
+ const content = readFileSync(gitignorePath, 'utf-8');
108
+ ig.add(content);
109
+ }
110
+ return ig;
111
+ }
112
+
113
+ /**
114
+ * Scan the repository and return an array of file entries.
115
+ * Each entry: { path: string (relative), absolutePath: string }
116
+ *
117
+ * @param {string} cwd - Root directory to scan
118
+ * @returns {Promise<Array<{ path: string, absolutePath: string }>>}
119
+ */
120
+ export async function scanRepository(cwd) {
121
+ const ig = loadGitignore(cwd);
122
+
123
+ const files = await fg(INCLUDE_PATTERNS, {
124
+ cwd,
125
+ ignore: IGNORE_PATTERNS,
126
+ dot: true,
127
+ followSymbolicLinks: false,
128
+ onlyFiles: true,
129
+ absolute: false,
130
+ });
131
+
132
+ // Filter through .gitignore rules
133
+ const filtered = files.filter((f) => {
134
+ try {
135
+ return !ig.ignores(f);
136
+ } catch {
137
+ return true;
138
+ }
139
+ });
140
+
141
+ // Sort for deterministic ordering
142
+ filtered.sort();
143
+
144
+ return filtered.map((f) => ({
145
+ path: f,
146
+ absolutePath: resolve(cwd, f),
147
+ }));
148
+ }
149
+
150
+ /**
151
+ * Read the content of a file safely (returns null if unreadable / too large).
152
+ * @param {string} absolutePath
153
+ * @param {number} maxBytes - default 80KB
154
+ * @returns {string|null}
155
+ */
156
+ export function readFileSafe(absolutePath, maxBytes = 80_000) {
157
+ try {
158
+ const content = readFileSync(absolutePath, 'utf-8');
159
+ if (content.length > maxBytes) {
160
+ return content.slice(0, maxBytes) + '\n\n[... truncated for context ...]';
161
+ }
162
+ return content;
163
+ } catch {
164
+ return null;
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Build a simple tree-style folder structure string.
170
+ * @param {Array<{path:string}>} files
171
+ * @returns {string}
172
+ */
173
+ export function buildFolderTree(files) {
174
+ const dirs = new Set();
175
+ files.forEach(({ path }) => {
176
+ const parts = path.split('/');
177
+ for (let i = 1; i < parts.length; i++) {
178
+ dirs.add(parts.slice(0, i).join('/'));
179
+ }
180
+ });
181
+
182
+ // Gather top-level entries
183
+ const topLevel = new Set();
184
+ files.forEach(({ path }) => {
185
+ topLevel.add(path.split('/')[0]);
186
+ });
187
+
188
+ let tree = './\n';
189
+ const allEntries = [
190
+ ...Array.from(dirs).sort(),
191
+ ...files.map((f) => f.path).filter((p) => !p.includes('/')),
192
+ ].sort();
193
+
194
+ const seen = new Set();
195
+ allEntries.forEach((entry) => {
196
+ if (seen.has(entry)) return;
197
+ seen.add(entry);
198
+ const depth = entry.split('/').length - 1;
199
+ const indent = ' '.repeat(depth);
200
+ const isDir = dirs.has(entry);
201
+ const name = entry.split('/').pop();
202
+ tree += `${indent}${isDir ? '📁 ' : '📄 '}${name}\n`;
203
+ });
204
+
205
+ return tree;
206
+ }
package/src/utils.js ADDED
@@ -0,0 +1,93 @@
1
+ /**
2
+ * utils.js
3
+ * Shared utilities for logging, formatting, and path helpers.
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+
8
+ // ── Logger ───────────────────────────────────────────────────────────────────
9
+
10
+ export const logger = {
11
+ info(msg) {
12
+ console.log(chalk.cyan(' ℹ ') + chalk.white(msg));
13
+ },
14
+ success(msg) {
15
+ console.log(chalk.green(' ✓ ') + chalk.white(msg));
16
+ },
17
+ warn(msg) {
18
+ console.log(chalk.yellow(' ⚠ ') + chalk.yellow(msg));
19
+ },
20
+ error(msg) {
21
+ console.error(chalk.red(' ✗ ') + chalk.red(msg));
22
+ },
23
+ dim(msg) {
24
+ console.log(chalk.dim(' ' + msg));
25
+ },
26
+ blank() {
27
+ console.log('');
28
+ },
29
+ divider() {
30
+ console.log(chalk.dim(' ' + '─'.repeat(50)));
31
+ },
32
+ header(title) {
33
+ console.log('');
34
+ console.log(chalk.bold.cyan(` ${title}`));
35
+ console.log(chalk.dim(' ' + '─'.repeat(50)));
36
+ },
37
+ };
38
+
39
+ // ── Formatting ───────────────────────────────────────────────────────────────
40
+
41
+ /**
42
+ * Format bytes into a human-readable string.
43
+ * @param {number} bytes
44
+ * @returns {string}
45
+ */
46
+ export function formatBytes(bytes) {
47
+ if (bytes < 1024) return `${bytes} B`;
48
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
49
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
50
+ }
51
+
52
+ /**
53
+ * Truncate a string to a max length with ellipsis.
54
+ * @param {string} str
55
+ * @param {number} max
56
+ * @returns {string}
57
+ */
58
+ export function truncate(str, max = 60) {
59
+ if (str.length <= max) return str;
60
+ return str.slice(0, max - 3) + '...';
61
+ }
62
+
63
+ /**
64
+ * Format elapsed time in ms to a readable string.
65
+ * @param {number} ms
66
+ * @returns {string}
67
+ */
68
+ export function formatDuration(ms) {
69
+ if (ms < 1000) return `${ms}ms`;
70
+ return `${(ms / 1000).toFixed(1)}s`;
71
+ }
72
+
73
+ // ── Validation ───────────────────────────────────────────────────────────────
74
+ // API key validation is handled by src/ai.js → validateProviderKey()
75
+
76
+ // ── Security ─────────────────────────────────────────────────────────────────
77
+
78
+ /**
79
+ * Sanitize an error message before printing to terminal.
80
+ * Redacts anything that looks like an API key or bearer token
81
+ * to prevent accidental credential leakage in logs.
82
+ *
83
+ * @param {string} msg
84
+ * @returns {string}
85
+ */
86
+ export function sanitizeErrorMessage(msg) {
87
+ if (!msg) return 'Unknown error';
88
+ return String(msg)
89
+ .replace(/sk-[A-Za-z0-9\-_]{10,}/g, 'sk-[REDACTED]')
90
+ .replace(/gsk_[A-Za-z0-9\-_]{10,}/g, 'gsk_[REDACTED]')
91
+ .replace(/Bearer\s+\S+/gi, 'Bearer [REDACTED]')
92
+ .replace(/Authorization:\s*\S+/gi, 'Authorization: [REDACTED]');
93
+ }