catport 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/ARCHITECTURE.md +94 -0
- package/CONTRIBUTING.md +133 -0
- package/LICENSE +21 -0
- package/README.md +414 -0
- package/bin/catport +8 -0
- package/package.json +48 -0
- package/src/cli/args.js +133 -0
- package/src/cli/main.js +78 -0
- package/src/cli/parser.js +152 -0
- package/src/cli/ui.js +78 -0
- package/src/config/constants.js +62 -0
- package/src/config/ignores.js +119 -0
- package/src/config/loader.js +15 -0
- package/src/config/options.js +181 -0
- package/src/core/analyzer.js +23 -0
- package/src/core/bundler.js +165 -0
- package/src/core/extractor.js +76 -0
- package/src/core/ignore.js +65 -0
- package/src/core/processor.js +59 -0
- package/src/core/scanner.js +184 -0
- package/src/formatters/index.js +78 -0
- package/src/formatters/json.js +284 -0
- package/src/formatters/markdown.js +164 -0
- package/src/formatters/multipart.js +127 -0
- package/src/formatters/xml.js +221 -0
- package/src/formatters/yaml.js +147 -0
- package/src/index.js +11 -0
- package/src/optimizers/definitions.js +79 -0
- package/src/optimizers/index.js +96 -0
- package/src/optimizers/langs/batch.js +3 -0
- package/src/optimizers/langs/c_family.js +3 -0
- package/src/optimizers/langs/clojure.js +3 -0
- package/src/optimizers/langs/css.js +3 -0
- package/src/optimizers/langs/go.js +5 -0
- package/src/optimizers/langs/haskell.js +4 -0
- package/src/optimizers/langs/html.js +4 -0
- package/src/optimizers/langs/ini.js +4 -0
- package/src/optimizers/langs/javascript.js +11 -0
- package/src/optimizers/langs/lua.js +4 -0
- package/src/optimizers/langs/markdown.js +3 -0
- package/src/optimizers/langs/perl.js +3 -0
- package/src/optimizers/langs/php.js +4 -0
- package/src/optimizers/langs/powershell.js +5 -0
- package/src/optimizers/langs/python.js +5 -0
- package/src/optimizers/langs/ruby.js +4 -0
- package/src/optimizers/langs/rust.js +3 -0
- package/src/optimizers/langs/shell.js +4 -0
- package/src/optimizers/langs/sql.js +4 -0
- package/src/optimizers/langs/xml.js +3 -0
- package/src/optimizers/langs/yaml.js +3 -0
- package/src/optimizers/tokenizer.js +444 -0
- package/src/utils/git.js +35 -0
- package/src/utils/io.js +79 -0
- package/src/utils/logger.js +25 -0
- package/src/utils/path.js +59 -0
- package/src/utils/style.js +59 -0
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "catport",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A high-performance, minimalist, and dependency-free CLI tool for bundling file contents or extracting files from a bundle.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"catport": "bin/catport"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"bin",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"CONTRIBUTING.md",
|
|
15
|
+
"ARCHITECTURE.md"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "node --test tests/**/*.test.js",
|
|
19
|
+
"test:watch": "node --test --watch tests/**/*.test.js",
|
|
20
|
+
"lint": "npx eslint src/ tests/ --max-warnings 0"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"llm",
|
|
24
|
+
"context",
|
|
25
|
+
"bundle",
|
|
26
|
+
"devtools",
|
|
27
|
+
"serializer",
|
|
28
|
+
"ai",
|
|
29
|
+
"prompt-engineering",
|
|
30
|
+
"multipart",
|
|
31
|
+
"yaml",
|
|
32
|
+
"git",
|
|
33
|
+
"minifier",
|
|
34
|
+
"zero-dependency",
|
|
35
|
+
"cli"
|
|
36
|
+
],
|
|
37
|
+
"author": "Reza Jelodar",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20.0.0"
|
|
41
|
+
},
|
|
42
|
+
"type": "module",
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@eslint/js": "^9.0.0",
|
|
45
|
+
"eslint": "^9.0.0",
|
|
46
|
+
"globals": "^15.0.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/cli/args.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { DEFAULTS, LOG, OPTIMIZE } from '../config/constants.js';
|
|
2
|
+
import { OPTIONS } from '../config/options.js';
|
|
3
|
+
import { Parser } from './parser.js';
|
|
4
|
+
|
|
5
|
+
const parseSize = (input) => {
|
|
6
|
+
if (typeof input === 'number') {
|
|
7
|
+
return input;
|
|
8
|
+
}
|
|
9
|
+
if (!input) {
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const s = String(input).trim();
|
|
14
|
+
const match = s.match(/^(\d+(?:\.\d+)?)\s*([kmg]b?)?$/i);
|
|
15
|
+
|
|
16
|
+
if (!match) {
|
|
17
|
+
return parseInt(s, 10) || 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const val = parseFloat(match[1]);
|
|
21
|
+
const suffix = (match[2] || '').toLowerCase();
|
|
22
|
+
|
|
23
|
+
if (suffix.startsWith('k')) {
|
|
24
|
+
return Math.floor(val * 1024);
|
|
25
|
+
}
|
|
26
|
+
if (suffix.startsWith('m')) {
|
|
27
|
+
return Math.floor(val * 1024 * 1024);
|
|
28
|
+
}
|
|
29
|
+
if (suffix.startsWith('g')) {
|
|
30
|
+
return Math.floor(val * 1024 * 1024 * 1024);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return Math.floor(val);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const normalize = (config) => {
|
|
37
|
+
if (config.verbose) {
|
|
38
|
+
config.logLevel = LOG.INFO;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (config.optimize !== undefined) {
|
|
42
|
+
const val = String(config.optimize).trim();
|
|
43
|
+
const lowerVal = val.toLowerCase();
|
|
44
|
+
const validModes = new Set(Object.values(OPTIMIZE));
|
|
45
|
+
|
|
46
|
+
if (validModes.has(lowerVal)) {
|
|
47
|
+
config.optimize = lowerVal;
|
|
48
|
+
} else {
|
|
49
|
+
config.optimizeCmd = val;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (config.noInstruct) {
|
|
54
|
+
config.instruct = false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (config.noStructure) {
|
|
58
|
+
config.structure = false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (config.unsafe) {
|
|
62
|
+
config.safeMode = false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (config.extensions) {
|
|
66
|
+
config.extSet = new Set(
|
|
67
|
+
config.extensions.split(',').map(e => e.trim().replace(/^\./, '').toLowerCase())
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Merge Ignore Patterns
|
|
72
|
+
const baseIgnore = config.noIgnore ? [] : DEFAULTS.IGNORE;
|
|
73
|
+
const userIgnore = config.ignore || [];
|
|
74
|
+
config.ignore = [...baseIgnore, ...userIgnore];
|
|
75
|
+
|
|
76
|
+
if (config.priority) {
|
|
77
|
+
config.priorityRules = config.priority.map(p => {
|
|
78
|
+
if (typeof p === 'object') {
|
|
79
|
+
return p;
|
|
80
|
+
}
|
|
81
|
+
const idx = p.lastIndexOf(':');
|
|
82
|
+
if (idx > 0) {
|
|
83
|
+
const score = parseInt(p.slice(idx + 1), 10);
|
|
84
|
+
if (!Number.isNaN(score)) {
|
|
85
|
+
return {
|
|
86
|
+
pattern: p.slice(0, idx),
|
|
87
|
+
score
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}).filter(Boolean);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (config.gitDiff === true) {
|
|
96
|
+
config.gitDiff = 'HEAD';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (config.maxSize !== undefined) {
|
|
100
|
+
config.maxSize = parseSize(config.maxSize);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return config;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const ArgParser = {
|
|
107
|
+
process(argv, fileConfig = {}, schema = OPTIONS) {
|
|
108
|
+
const defaults = Parser.getDefaults(schema);
|
|
109
|
+
const explicit = Parser.parse(argv, schema);
|
|
110
|
+
|
|
111
|
+
const rawConfig = {
|
|
112
|
+
logLevel: LOG.WARN,
|
|
113
|
+
instruct: DEFAULTS.INSTRUCT,
|
|
114
|
+
structure: DEFAULTS.STRUCTURE,
|
|
115
|
+
listDirs: DEFAULTS.LIST_DIRS,
|
|
116
|
+
optimize: DEFAULTS.OPTIMIZE,
|
|
117
|
+
safeMode: DEFAULTS.SAFE_MODE,
|
|
118
|
+
charsPerToken: DEFAULTS.CHARS_PER_TOKEN,
|
|
119
|
+
maxSize: DEFAULTS.MAX_SIZE,
|
|
120
|
+
...defaults,
|
|
121
|
+
...fileConfig
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
Object.keys(explicit).forEach(k => {
|
|
125
|
+
if (k === 'paths' && explicit.paths.length === 0) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
rawConfig[k] = explicit[k];
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return normalize(rawConfig);
|
|
132
|
+
}
|
|
133
|
+
};
|
package/src/cli/main.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ArgParser } from './args.js';
|
|
2
|
+
import { UI } from './ui.js';
|
|
3
|
+
import { Bundler } from '../core/bundler.js';
|
|
4
|
+
import { Extractor } from '../core/extractor.js';
|
|
5
|
+
import { NodeIO } from '../utils/io.js';
|
|
6
|
+
import { Git } from '../utils/git.js';
|
|
7
|
+
import { Logger } from '../utils/logger.js';
|
|
8
|
+
import { EXIT, APP } from '../config/constants.js';
|
|
9
|
+
import { OPTIONS } from '../config/options.js';
|
|
10
|
+
import { Optimizer } from '../optimizers/index.js';
|
|
11
|
+
import { Formatter } from '../formatters/index.js';
|
|
12
|
+
import { Scanner } from '../core/scanner.js';
|
|
13
|
+
import { Analyzer } from '../core/analyzer.js';
|
|
14
|
+
import { Processor } from '../core/processor.js';
|
|
15
|
+
import { ConfigLoader } from '../config/loader.js';
|
|
16
|
+
|
|
17
|
+
export const CLI = {
|
|
18
|
+
async run(argv, io = NodeIO) {
|
|
19
|
+
const ui = UI(io);
|
|
20
|
+
try {
|
|
21
|
+
const fileConfig = await ConfigLoader.load(io);
|
|
22
|
+
const config = ArgParser.process(argv, fileConfig);
|
|
23
|
+
|
|
24
|
+
if (config.help) {
|
|
25
|
+
ui.printHeader(APP);
|
|
26
|
+
ui.printHelp(APP, OPTIONS);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (config.version) {
|
|
31
|
+
io.writeStdout(`${APP.NAME} v${APP.VERSION}\n`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const logger = Logger(config.logLevel, io);
|
|
36
|
+
|
|
37
|
+
if (config.extract) {
|
|
38
|
+
await Extractor.run(config, io, {
|
|
39
|
+
formatter: Formatter,
|
|
40
|
+
logger: logger
|
|
41
|
+
});
|
|
42
|
+
} else {
|
|
43
|
+
if (config.gitDiff) {
|
|
44
|
+
const changes = await Git.getChangedFiles(io, config.gitDiff, io.cwd());
|
|
45
|
+
if (!changes) {
|
|
46
|
+
ui.printError('Not a git repository or git command failed.');
|
|
47
|
+
io.exit(EXIT.ERROR);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (changes.size === 0) {
|
|
52
|
+
ui.printError('No changes detected.');
|
|
53
|
+
io.exit(EXIT.SUCCESS);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
config.gitFiles = changes;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const stats = await Bundler.run(config, io, {
|
|
61
|
+
scanner: Scanner,
|
|
62
|
+
analyzer: Analyzer,
|
|
63
|
+
processor: Processor,
|
|
64
|
+
optimizer: Optimizer,
|
|
65
|
+
formatter: Formatter,
|
|
66
|
+
logger: logger
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (config.output && stats) {
|
|
70
|
+
ui.printUsageReport(stats, config.output);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch (e) {
|
|
74
|
+
ui.printError(e.message);
|
|
75
|
+
io.exit(EXIT.ERROR);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* Map CLI flags to schema keys.
|
|
4
|
+
*/
|
|
5
|
+
const buildMaps = (schema) => {
|
|
6
|
+
const flags = new Map();
|
|
7
|
+
const shorts = new Map();
|
|
8
|
+
|
|
9
|
+
for (const [key, opt] of Object.entries(schema)) {
|
|
10
|
+
const long = '--' + key.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
11
|
+
flags.set(long, key);
|
|
12
|
+
if (opt.short) {
|
|
13
|
+
shorts.set('-' + opt.short, key);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return { flags, shorts };
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const parseNumber = (key, val) => {
|
|
20
|
+
const n = Number(val);
|
|
21
|
+
if (Number.isNaN(n)) {
|
|
22
|
+
throw new Error(`Expected number for --${key}, got "${val}"`);
|
|
23
|
+
}
|
|
24
|
+
return n;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const Parser = {
|
|
28
|
+
getDefaults(schema) {
|
|
29
|
+
const defaults = { paths: [] };
|
|
30
|
+
for (const [key, opt] of Object.entries(schema)) {
|
|
31
|
+
if (opt.default !== undefined) {
|
|
32
|
+
defaults[key] = opt.default;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return defaults;
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Parse argv into an explicit configuration object.
|
|
40
|
+
* Does NOT apply defaults. Returns only what is present in argv.
|
|
41
|
+
*/
|
|
42
|
+
parse(argv, schema) {
|
|
43
|
+
const { flags, shorts } = buildMaps(schema);
|
|
44
|
+
const explicit = { paths: [] };
|
|
45
|
+
const args = argv.slice(2);
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < args.length; i++) {
|
|
48
|
+
const arg = args[i];
|
|
49
|
+
|
|
50
|
+
if (arg === '--') {
|
|
51
|
+
explicit.paths.push(...args.slice(i + 1));
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!arg.startsWith('-') || arg === '-') {
|
|
56
|
+
explicit.paths.push(arg);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Grouped Short Flags (e.g. -xvu)
|
|
61
|
+
if (!arg.startsWith('--') && arg.length > 2) {
|
|
62
|
+
const chars = arg.slice(1).split('');
|
|
63
|
+
for (const c of chars) {
|
|
64
|
+
const key = shorts.get('-' + c);
|
|
65
|
+
if (!key) {
|
|
66
|
+
throw new Error(`Unknown flag: -${c}`);
|
|
67
|
+
}
|
|
68
|
+
if (schema[key].type !== 'boolean') {
|
|
69
|
+
throw new Error(`Flag -${c} requires a value and cannot be grouped.`);
|
|
70
|
+
}
|
|
71
|
+
explicit[key] = true;
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Normal Flags
|
|
77
|
+
let key = null;
|
|
78
|
+
let explicitVal = null;
|
|
79
|
+
|
|
80
|
+
if (arg.startsWith('--')) {
|
|
81
|
+
if (arg.includes('=')) {
|
|
82
|
+
const parts = arg.split('=');
|
|
83
|
+
key = flags.get(parts[0]);
|
|
84
|
+
explicitVal = parts.slice(1).join('=');
|
|
85
|
+
} else {
|
|
86
|
+
key = flags.get(arg);
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
key = shorts.get(arg);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!key) {
|
|
93
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const opt = schema[key];
|
|
97
|
+
const isBool = opt.type === 'boolean';
|
|
98
|
+
|
|
99
|
+
if (isBool) {
|
|
100
|
+
if (explicitVal) {
|
|
101
|
+
throw new Error(`Boolean flag ${key} does not accept a value.`);
|
|
102
|
+
}
|
|
103
|
+
explicit[key] = true;
|
|
104
|
+
} else {
|
|
105
|
+
let val = explicitVal;
|
|
106
|
+
|
|
107
|
+
if (val === null) {
|
|
108
|
+
const next = args[i + 1];
|
|
109
|
+
|
|
110
|
+
// Value detection logic
|
|
111
|
+
let isValidValue = false;
|
|
112
|
+
if (next !== undefined) {
|
|
113
|
+
const isFlag = next.startsWith('-');
|
|
114
|
+
const isStdin = next === '-';
|
|
115
|
+
const isNumberValue = isFlag && opt.type === 'number' && !Number.isNaN(Number(next));
|
|
116
|
+
|
|
117
|
+
if (!isFlag || isStdin || isNumberValue) {
|
|
118
|
+
isValidValue = true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (isValidValue) {
|
|
123
|
+
val = next;
|
|
124
|
+
i++;
|
|
125
|
+
} else {
|
|
126
|
+
if (opt.optional) {
|
|
127
|
+
val = true;
|
|
128
|
+
} else {
|
|
129
|
+
throw new Error(`Flag ${key} requires a value.`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (val === true && opt.optional) {
|
|
135
|
+
explicit[key] = true;
|
|
136
|
+
} else {
|
|
137
|
+
const parsed = opt.type === 'number' ? parseNumber(key, val) : val;
|
|
138
|
+
if (opt.type === 'array') {
|
|
139
|
+
if (!explicit[key]) {
|
|
140
|
+
explicit[key] = [];
|
|
141
|
+
}
|
|
142
|
+
explicit[key].push(parsed);
|
|
143
|
+
} else {
|
|
144
|
+
explicit[key] = parsed;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return explicit;
|
|
151
|
+
}
|
|
152
|
+
};
|
package/src/cli/ui.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Style } from '../utils/style.js';
|
|
2
|
+
|
|
3
|
+
export const UI = (io) => ({
|
|
4
|
+
printHeader: (app) => {
|
|
5
|
+
io.writeStdout(`\n${Style.bold(Style.cyan(app.NAME))} v${app.VERSION}\n`);
|
|
6
|
+
io.writeStdout(`${Style.dim(app.DESC)}\n`);
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
printHelp: (app, schema) => {
|
|
10
|
+
const pad = (str, len) => str.padEnd(len, ' ');
|
|
11
|
+
const row = (short, long, desc) => ` ${Style.yellow(short)}, ${Style.green(pad(long, 24))} ${desc}`;
|
|
12
|
+
const toKebab = (str) => '--' + str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
13
|
+
|
|
14
|
+
io.writeStdout(`\n${Style.bold('USAGE')}\n ${app.NAME} [options] [path...]\n`);
|
|
15
|
+
|
|
16
|
+
const categories = {};
|
|
17
|
+
|
|
18
|
+
Object.entries(schema).forEach(([key, opt]) => {
|
|
19
|
+
const cat = opt.category || 'General';
|
|
20
|
+
if (!categories[cat]) {
|
|
21
|
+
categories[cat] = [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let flagStr = toKebab(key);
|
|
25
|
+
if (opt.type !== 'boolean') {
|
|
26
|
+
const mv = Style.italic(opt.metavar);
|
|
27
|
+
if (opt.optional) {
|
|
28
|
+
flagStr += ` [${mv}]`;
|
|
29
|
+
} else {
|
|
30
|
+
flagStr += ` <${mv}>`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
categories[cat].push(row(`-${opt.short}`, flagStr, opt.desc));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const order = [
|
|
38
|
+
'General',
|
|
39
|
+
'Bundling',
|
|
40
|
+
'Extraction'
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
order.forEach(name => {
|
|
44
|
+
if (categories[name]) {
|
|
45
|
+
io.writeStdout(`\n${Style.bold(Style.underline(name.toUpperCase()))}\n${categories[name].join('\n')}\n`);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const cmt = (s) => Style.dim('# ' + s);
|
|
50
|
+
|
|
51
|
+
io.writeStdout(`\n${Style.bold('EXAMPLES')}
|
|
52
|
+
${cmt('Bundle project with task')}
|
|
53
|
+
${app.NAME} -T "Refactor this" -o bundle.md
|
|
54
|
+
|
|
55
|
+
${cmt('Bundle git changes with optimized output (no comments)')}
|
|
56
|
+
${app.NAME} -g HEAD -O comments
|
|
57
|
+
|
|
58
|
+
${cmt('Extract code (safe by default)')}
|
|
59
|
+
${app.NAME} -x bundle.md -d ./src
|
|
60
|
+
|
|
61
|
+
${cmt('Extract code (unsafe/allow traversal)')}
|
|
62
|
+
${app.NAME} -x bundle.md -d ./src -U
|
|
63
|
+
`);
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
printError: (msg) => {
|
|
67
|
+
const badge = Style.bgRed(Style.white(' FATAL '));
|
|
68
|
+
io.writeStderr(`\n${badge} ${msg}\n`);
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
printUsageReport: (stats, path) => {
|
|
72
|
+
if (path) {
|
|
73
|
+
const files = stats.files !== undefined ? stats.files : stats;
|
|
74
|
+
const tokens = stats.tokens !== undefined ? ` (~${stats.tokens} tokens)` : '';
|
|
75
|
+
io.writeStdout(`\n${Style.green('✔ Success')} Bundled ${Style.bold(files)} files${tokens} to ${Style.cyan(path)}\n`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { DEFAULT_IGNORES } from './ignores.js';
|
|
2
|
+
|
|
3
|
+
export const APP = {
|
|
4
|
+
NAME: 'catport',
|
|
5
|
+
VERSION: '1.0.0',
|
|
6
|
+
DESC: 'Deterministic filesystem serializer for LLM contexts'
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const EXIT = {
|
|
10
|
+
SUCCESS: 0,
|
|
11
|
+
ERROR: 1
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const LOG = {
|
|
15
|
+
SILENT: 0,
|
|
16
|
+
ERROR: 1,
|
|
17
|
+
WARN: 2,
|
|
18
|
+
INFO: 3,
|
|
19
|
+
DEBUG: 4
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const FORMAT = {
|
|
23
|
+
MD: 'md',
|
|
24
|
+
XML: 'xml',
|
|
25
|
+
JSON: 'json',
|
|
26
|
+
YAML: 'yaml',
|
|
27
|
+
MULTIPART: 'multipart'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const XML_MODE = {
|
|
31
|
+
AUTO: 'auto',
|
|
32
|
+
CDATA: 'cdata',
|
|
33
|
+
ESCAPE: 'escape'
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const OPTIMIZE = {
|
|
37
|
+
NONE: 'none',
|
|
38
|
+
WHITESPACE: 'whitespace',
|
|
39
|
+
COMMENTS: 'comments',
|
|
40
|
+
MINIFY: 'minify'
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const XML_TAGS = {
|
|
44
|
+
CDATA_OPEN: '<![' + 'CDATA[',
|
|
45
|
+
CDATA_CLOSE: ']]' + '>'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const DEFAULTS = {
|
|
49
|
+
BUDGET: 0,
|
|
50
|
+
CHARS_PER_TOKEN: 4.2,
|
|
51
|
+
PRIORITY: 1,
|
|
52
|
+
EXTRACT_DIR: '.',
|
|
53
|
+
FORMAT: FORMAT.MD,
|
|
54
|
+
INSTRUCT: true,
|
|
55
|
+
STRUCTURE: true,
|
|
56
|
+
LIST_DIRS: false,
|
|
57
|
+
SAFE_MODE: true,
|
|
58
|
+
OPTIMIZE: OPTIMIZE.NONE,
|
|
59
|
+
CONCURRENCY: 32,
|
|
60
|
+
MAX_SIZE: 10 * 1024 * 1024,
|
|
61
|
+
IGNORE: DEFAULT_IGNORES
|
|
62
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export const DEFAULT_IGNORES = [
|
|
2
|
+
// --- Version Control & Metadata ---
|
|
3
|
+
'.git/', '.svn/', '.hg/', '.bzr/', '.cvs/', '.gitattributes', '.gitmodules',
|
|
4
|
+
|
|
5
|
+
// --- OS & System Files ---
|
|
6
|
+
'.DS_Store', '.DS_Store?', '._*', '.Spotlight-V100', '.Trashes',
|
|
7
|
+
'Thumbs.db', 'ehthumbs.db', 'desktop.ini', '$RECYCLE.BIN/',
|
|
8
|
+
'.AppleDouble/', '.LSOverride/',
|
|
9
|
+
'.directory', '.fuse_hidden*', '.nfs*',
|
|
10
|
+
|
|
11
|
+
// --- Editors & IDEs ---
|
|
12
|
+
'.idea/', '.vscode/', '.vs/', '.settings/', '.classpath', '.project',
|
|
13
|
+
'*.sublime-workspace', '*.sublime-project',
|
|
14
|
+
'*.iml', '*.ipr', '*.iws', '*.suo', '*.ntvs*', '*.njsproj', '*.sln',
|
|
15
|
+
'.factorypath', '.checkstyle', '.metadata/',
|
|
16
|
+
'nbproject/', 'build.xml', // NetBeans / Ant
|
|
17
|
+
'.history/', '.ionide/', // VS Code history / Ionide
|
|
18
|
+
'Session.vim', '.netrwhist', // Vim
|
|
19
|
+
'*~', '#*#', '.#*', // Emacs / Vim
|
|
20
|
+
|
|
21
|
+
// --- Logs, Temp, Backups ---
|
|
22
|
+
'*.log', 'logs/', 'log/', '*.log.*',
|
|
23
|
+
'*.backup', '*.bak', '*.tmp', '*.temp',
|
|
24
|
+
'*.swp', '*.swo', '*.swn',
|
|
25
|
+
'*.old', '*.orig', '*.rej',
|
|
26
|
+
'*.dump', '*.stackdump', 'core',
|
|
27
|
+
'*.pid', '*.seed',
|
|
28
|
+
|
|
29
|
+
// --- Secrets, Credentials, Local Config (Security) ---
|
|
30
|
+
'.env', '.env.*', '!.env.example', '!.env.sample', '!.env.template', '!.env.defaults',
|
|
31
|
+
'*.pem', '*.key', '*.crt', '*.pfx', '*.p12', '*.der', '*.cer',
|
|
32
|
+
'id_rsa', 'id_rsa.pub', 'known_hosts',
|
|
33
|
+
'config.secret.*', 'secrets.*', 'credentials.*',
|
|
34
|
+
'.npmrc', '.netrc', '.ssh/', '.aws/', '.gcloud/', '.azure/',
|
|
35
|
+
|
|
36
|
+
// --- Build Outputs & Artifacts (General) ---
|
|
37
|
+
'dist/', 'build/', 'out/', 'target/', 'obj/', 'bin/',
|
|
38
|
+
'release/', 'debug/',
|
|
39
|
+
'_build/', 'deps/',
|
|
40
|
+
|
|
41
|
+
// --- Archives & Binary Assets ---
|
|
42
|
+
'*.zip', '*.tar', '*.gz', '*.tgz', '*.rar', '*.7z', '*.iso', '*.dmg',
|
|
43
|
+
'*.exe', '*.dll', '*.so', '*.so.*', '*.dylib', '*.bin', '*.msi', '*.app',
|
|
44
|
+
'*.deb', '*.rpm', '*.apk', '*.jar', '*.war', '*.ear', '*.nar',
|
|
45
|
+
'*.png', '*.jpg', '*.jpeg', '*.gif', '*.ico', '*.svg', '*.webp', '*.bmp', '*.tiff',
|
|
46
|
+
'*.mp4', '*.mp3', '*.wav', '*.mov', '*.avi', '*.mkv', '*.webm', '*.flv',
|
|
47
|
+
'*.pdf', '*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx',
|
|
48
|
+
'*.ttf', '*.otf', '*.woff', '*.woff2', '*.eot',
|
|
49
|
+
'*.psd', '*.ai', '*.hex', '*.out',
|
|
50
|
+
|
|
51
|
+
// --- Node.js / JS / TS ---
|
|
52
|
+
'node_modules/', 'bower_components/', 'jspm_packages/', 'web_modules/',
|
|
53
|
+
'.npm/', '.yarn/', '.pnp.*', '.pnpm-store/',
|
|
54
|
+
'.cache/', '.parcel-cache/', '.eslintcache/', '.stylelintcache/',
|
|
55
|
+
'coverage/', '.nyc_output/', 'lib-cov/',
|
|
56
|
+
'.grunt/', '.gulp/',
|
|
57
|
+
'.next/', '.nuxt/', '.output/', '.docusaurus/', '.svelte-kit/', '.astro/',
|
|
58
|
+
'*.min.js', '*.min.css', '*.map', 'report.[0-9]*.*', '*.tsbuildinfo',
|
|
59
|
+
|
|
60
|
+
// --- Python ---
|
|
61
|
+
'__pycache__/', '*.py[cod]', '*$py.class',
|
|
62
|
+
'venv/', '.venv/', 'env/', 'ENV/',
|
|
63
|
+
'.pytest_cache/', '.mypy_cache/', '.ruff_cache/', '.tox/', '.nox/', '.coverage',
|
|
64
|
+
'htmlcov/', 'pip-wheel-metadata/',
|
|
65
|
+
'*.egg-info/', '.eggs/', '*.egg', 'instance/',
|
|
66
|
+
'jupyter_cache/', '.ipynb_checkpoints/', 'celerybeat-schedule',
|
|
67
|
+
|
|
68
|
+
// --- Java / JVM / Kotlin ---
|
|
69
|
+
'*.class', '.gradle/', 'gradle-app.setting',
|
|
70
|
+
'hs_err_pid*',
|
|
71
|
+
|
|
72
|
+
// --- C / C++ / Obj-C ---
|
|
73
|
+
'*.o', '*.obj', '*.lo', '*.la', '*.lai', '*.slo', '*.sl',
|
|
74
|
+
'*.a', '*.lib',
|
|
75
|
+
'*.gch', '*.pch', '*.d',
|
|
76
|
+
'.make', 'cmake-build-*/', 'CMakeFiles/', 'CMakeCache.txt',
|
|
77
|
+
'*.profraw', '*.gcda', '*.gcno',
|
|
78
|
+
'DerivedData/', 'compile_commands.json',
|
|
79
|
+
|
|
80
|
+
// --- .NET / C# ---
|
|
81
|
+
'*.pdb', 'packages/', 'TestResult/',
|
|
82
|
+
|
|
83
|
+
// --- Go ---
|
|
84
|
+
'go.work', 'go.test', '_testmain.go',
|
|
85
|
+
|
|
86
|
+
// --- Rust ---
|
|
87
|
+
'*.rs.bk',
|
|
88
|
+
|
|
89
|
+
// --- Ruby ---
|
|
90
|
+
'.bundle/', 'lib/bundler/man/', '*.gem', '.ruby-version',
|
|
91
|
+
|
|
92
|
+
// --- PHP ---
|
|
93
|
+
'vendor/', 'composer.phar', '*.phar', '.phpunit.result.cache',
|
|
94
|
+
|
|
95
|
+
// --- Swift ---
|
|
96
|
+
'.swiftpm',
|
|
97
|
+
|
|
98
|
+
// --- Dart / Flutter ---
|
|
99
|
+
'.dart_tool/', '.pub-cache/', '.pub/',
|
|
100
|
+
|
|
101
|
+
// --- Terraform ---
|
|
102
|
+
'.terraform/', '*.tfstate', '.terraform.lock.hcl',
|
|
103
|
+
|
|
104
|
+
// --- Serverless ---
|
|
105
|
+
'.serverless/', '.aws-sam/', '.elasticbeanstalk/',
|
|
106
|
+
|
|
107
|
+
// --- Virtualization ---
|
|
108
|
+
'.vagrant/',
|
|
109
|
+
|
|
110
|
+
// --- Databases ---
|
|
111
|
+
'*.sqlite', '*.sqlite3', '*.db', '*.db3', '*.s3db',
|
|
112
|
+
'*.db-journal', '*.mdb', '*.accdb',
|
|
113
|
+
'dump.rdb',
|
|
114
|
+
|
|
115
|
+
// --- Lock Files (Noise reduction) ---
|
|
116
|
+
'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb',
|
|
117
|
+
'Cargo.lock', 'Gemfile.lock', 'composer.lock', 'poetry.lock', 'Pipfile.lock',
|
|
118
|
+
'go.sum', 'mix.lock', 'pubspec.lock', 'flake.lock'
|
|
119
|
+
];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
|
|
3
|
+
export const ConfigLoader = {
|
|
4
|
+
load: async (io) => {
|
|
5
|
+
try {
|
|
6
|
+
const path = resolve(io.cwd(), '.catport.json');
|
|
7
|
+
const content = await io.readText(path);
|
|
8
|
+
return JSON.parse(content);
|
|
9
|
+
} catch {
|
|
10
|
+
// Fail silently if config file is missing or invalid,
|
|
11
|
+
// relying on defaults handled by ArgParser.
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|