llmview 0.1.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/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2026
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any
4
+ purpose with or without fee is hereby granted, provided that the above
5
+ copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # llmview
2
+
3
+ `llmview` is a small command-line tool for producing repeatable views of code for LLMs. You can configure views for different aspects of a large project and reuse them as the code evolves.
4
+
5
+ ## Quick start
6
+
7
+ Install from npm
8
+
9
+ ```bash
10
+ npm install -g llmview
11
+ ```
12
+
13
+ Create any number of view files in your project. These can be saved anywhere. For example, a full-stack monorepo:
14
+
15
+ ```
16
+ .views/
17
+ backend.llmview
18
+ frontend.llmview
19
+ integration_tests.llmview
20
+ new_feature.llmview
21
+ ```
22
+
23
+ And use them selectively:
24
+
25
+ ```
26
+ llmview .views/backend.llmview
27
+ ```
28
+
29
+ Run `llmview --help` for all options.
30
+
31
+ ## How it works
32
+
33
+ A view is a list of glob patterns to select. It's the same format as `.gitignore`, but it says which patterns to select rather than ignore.
34
+
35
+ ```gitignore
36
+ # Code
37
+ backend/**/**
38
+ !backend/migrations/**
39
+
40
+ # Docs
41
+ docs/style_guide.md
42
+ ```
43
+
44
+ It will find all files that satisfy the glob patterns. It also respects existing `.gitignore` files (even nested ones) if your project is version controlled. If any files are ignored by git, they are also ignored here even if the view file would have selected it.
45
+
46
+ After selecting, it serializes the contents of each file into a LLM-friendly format and prints to stdout.
47
+
48
+ ````
49
+ ```
50
+ <file path="backend/main.py">
51
+ from flask import Flask
52
+
53
+ app = Flask(__name__)
54
+
55
+ @app.route("/")
56
+ def hello():
57
+ return "Hello, World!"
58
+
59
+ if __name__ == "__main__":
60
+ app.run(debug=True)
61
+
62
+ </file>
63
+ ```
64
+
65
+ ```
66
+ <file path="docs/style_guide.md">
67
+ # Style guide
68
+
69
+ Make no mistakes
70
+
71
+ </file>
72
+ ```
73
+ ````
74
+
75
+ ### Including the project directory
76
+
77
+ The `-t` argument includes the file system hierarchy for all selected files at the beginning of the result.
78
+
79
+ ````
80
+ ```
81
+ <directory>
82
+ my_project/
83
+ backend/
84
+ main.py
85
+ docs/
86
+ style_guide.md
87
+ </directory>
88
+ ```
89
+
90
+ ```
91
+ <file path="my_project/backend/main.py">
92
+ ...
93
+ ````
94
+
95
+ ### Including line numbers
96
+
97
+ The `-n` argument includes line numbers in each file, similar to `cat -n`. This uses more tokens, but can also be useful context.
98
+
99
+ ## Integrating with your tools
100
+
101
+ For agentic tools, you can ask an agent to use the `llmview` command itself with views you defined. This might save it some time getting up to speed on your project, especially across sessions.
102
+
103
+ For interactive use such as code reviews, you can pipe the results to an API or just copy the contents to your clipboard and paste into a LLM web app, like:
104
+
105
+ ```bash
106
+ llmview .views/backend.llmview | pbcopy
107
+ ```
108
+
109
+ ## Renderers
110
+
111
+ This tool comes with a set of opinionated file renderers. They are informed by the file extension. Exceptions are made for:
112
+
113
+ - CSV (truncated by default, preserving the header and the first 10 lines)
114
+ - Excel, media files, and other non-text formats (omitted)
115
+
116
+ There is also a max size of 250KB per file. If a file is larger than that, it is not rendered.
117
+
118
+ ## Dry run to get statistics
119
+
120
+ To see what files will be included and an estimate of tokens used, try a command like this:
121
+
122
+ ```bash
123
+ llmview -v .views/backend.llmview > /dev/null
124
+ ```
125
+
126
+ This works because verbose information gets printed to stderr, and stdout goes to `/dev/null`.
package/dist/build.js ADDED
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.buildDirectory = void 0;
37
+ const os = __importStar(require("os"));
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const git_1 = require("./git");
41
+ const constants_1 = require("./constants");
42
+ const expandUser = (inputPath) => {
43
+ return inputPath.replace(/^~/, os.homedir());
44
+ };
45
+ const buildDirectory = (projectPath) => {
46
+ projectPath = expandUser(projectPath);
47
+ const ignores = [
48
+ { ig: (0, git_1.createIgnore)(constants_1.BASE_IGNORE_CONTENT), scope: '' },
49
+ ];
50
+ const rootGitignore = (0, git_1.createIgnoreFromFile)(path.join(projectPath, '.gitignore'));
51
+ if (rootGitignore) {
52
+ ignores.push({ ig: rootGitignore, scope: '' });
53
+ }
54
+ const stat = fs.statSync(projectPath);
55
+ const rootNode = {
56
+ ino: stat.ino,
57
+ name: path.basename(projectPath),
58
+ relativePath: '',
59
+ createdAt: stat.birthtime,
60
+ updatedAt: stat.mtime,
61
+ type: 'directory',
62
+ rootPath: projectPath,
63
+ children: [],
64
+ };
65
+ rootNode.children = buildChildrenNodes(projectPath, rootNode, ignores);
66
+ return rootNode;
67
+ };
68
+ exports.buildDirectory = buildDirectory;
69
+ const buildChildrenNodes = (rootPath, parentNode, ignores) => {
70
+ const currentPath = path.join(rootPath, parentNode.relativePath);
71
+ const entries = fs.readdirSync(currentPath);
72
+ const nodes = [];
73
+ for (const entry of entries) {
74
+ const entryFullPath = path.join(currentPath, entry);
75
+ const lstat = fs.lstatSync(entryFullPath);
76
+ if (lstat.isSymbolicLink()) {
77
+ continue;
78
+ }
79
+ const isDirectory = lstat.isDirectory();
80
+ const nodeRelativePath = parentNode.relativePath
81
+ ? `${parentNode.relativePath}/${entry}`
82
+ : entry;
83
+ if ((0, git_1.isPathIgnored)(nodeRelativePath, isDirectory, ignores)) {
84
+ continue;
85
+ }
86
+ const nodeBase = {
87
+ ino: lstat.ino,
88
+ name: entry,
89
+ relativePath: nodeRelativePath,
90
+ createdAt: lstat.birthtime,
91
+ updatedAt: lstat.mtime,
92
+ };
93
+ if (isDirectory) {
94
+ const dirNode = {
95
+ ...nodeBase,
96
+ type: 'directory',
97
+ children: [],
98
+ };
99
+ const nestedIg = (0, git_1.createIgnoreFromFile)(path.join(entryFullPath, '.gitignore'));
100
+ const childIgnores = nestedIg
101
+ ? [...ignores, { ig: nestedIg, scope: nodeRelativePath }]
102
+ : ignores;
103
+ dirNode.children = buildChildrenNodes(rootPath, dirNode, childIgnores);
104
+ nodes.push(dirNode);
105
+ }
106
+ else {
107
+ const fileNode = {
108
+ ...nodeBase,
109
+ type: 'file',
110
+ };
111
+ nodes.push(fileNode);
112
+ }
113
+ }
114
+ return nodes.sort((a, b) => {
115
+ if (a.type !== b.type) {
116
+ return a.type === 'directory' ? -1 : 1;
117
+ }
118
+ return a.name.localeCompare(b.name);
119
+ });
120
+ };
package/dist/cli.js ADDED
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const build_1 = require("./build");
40
+ const render_1 = require("./render");
41
+ const select_1 = require("./select");
42
+ const parseLlmviewFile = (filePath) => {
43
+ const content = fs.readFileSync(filePath, 'utf-8');
44
+ return content
45
+ .split('\n')
46
+ .map((line) => line.trim())
47
+ .filter((line) => line !== '' && !line.startsWith('#'));
48
+ };
49
+ const HELP_TEXT = `llmview - Generate LLM context from codebases using gitignore-style patterns
50
+
51
+ Usage: llmview [options] <view-file>
52
+
53
+ Arguments:
54
+ <view-file> Path to a .llmview file containing glob patterns
55
+
56
+ Options:
57
+ -n, --number Include line numbers in output
58
+ -t, --tree Include directory tree of selected files
59
+ -v, --verbose Print file statistics to stderr
60
+ -h, --help Show this help message
61
+ `;
62
+ const main = () => {
63
+ const args = process.argv.slice(2);
64
+ if (args.includes('-h') || args.includes('--help')) {
65
+ console.log(HELP_TEXT);
66
+ process.exit(0);
67
+ }
68
+ const verbose = args.includes('-v') || args.includes('--verbose');
69
+ const includeTree = args.includes('-t') || args.includes('--tree');
70
+ const lineNumbers = args.includes('-n') || args.includes('--number');
71
+ const llmviewPath = args.find((arg) => !arg.startsWith('-'));
72
+ if (!llmviewPath) {
73
+ console.log(HELP_TEXT);
74
+ process.exit(1);
75
+ }
76
+ const resolvedPath = path.resolve(llmviewPath);
77
+ if (!fs.existsSync(resolvedPath)) {
78
+ console.error(`File not found: ${resolvedPath}`);
79
+ process.exit(1);
80
+ }
81
+ const patterns = parseLlmviewFile(resolvedPath);
82
+ if (patterns.length === 0) {
83
+ console.error('No patterns found in llmview file');
84
+ process.exit(1);
85
+ }
86
+ const rootNode = (0, build_1.buildDirectory)(process.cwd());
87
+ const { selectedFiles, visiblePaths } = (0, select_1.selectFiles)(rootNode, patterns, {
88
+ verbose,
89
+ });
90
+ let output = '';
91
+ if (includeTree) {
92
+ output += (0, render_1.renderHierarchy)(rootNode, { verbose }, visiblePaths);
93
+ }
94
+ output += (0, render_1.renderFiles)(rootNode.rootPath, selectedFiles, {
95
+ verbose,
96
+ lineNumbers,
97
+ });
98
+ const estimatedTokens = Math.ceil(output.length / 4);
99
+ if (verbose) {
100
+ console.warn(`Estimated tokens: ${estimatedTokens}`);
101
+ }
102
+ console.log(output);
103
+ };
104
+ main();
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_FILE_SIZE_KB = exports.BASE_IGNORE_CONTENT = void 0;
4
+ exports.BASE_IGNORE_CONTENT = `.git
5
+ .DS_Store
6
+ __pycache__/
7
+ node_modules/`;
8
+ exports.MAX_FILE_SIZE_KB = 250;
package/dist/git.js ADDED
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.isPathIgnored = exports.createIgnore = exports.createIgnoreFromFile = void 0;
40
+ const fs = __importStar(require("fs"));
41
+ const ignore_1 = __importDefault(require("ignore"));
42
+ const createIgnoreFromFile = (gitignorePath) => {
43
+ if (!fs.existsSync(gitignorePath)) {
44
+ return undefined;
45
+ }
46
+ return (0, ignore_1.default)().add(fs.readFileSync(gitignorePath, 'utf8'));
47
+ };
48
+ exports.createIgnoreFromFile = createIgnoreFromFile;
49
+ const createIgnore = (content) => {
50
+ return (0, ignore_1.default)().add(content);
51
+ };
52
+ exports.createIgnore = createIgnore;
53
+ const isPathIgnored = (relativePath, isDirectory, ignores) => {
54
+ const pathToCheck = isDirectory ? `${relativePath}/` : relativePath;
55
+ let ignored = false;
56
+ for (const { ig, scope } of ignores) {
57
+ const localPath = scope === ''
58
+ ? pathToCheck
59
+ : pathToCheck.startsWith(scope + '/')
60
+ ? pathToCheck.slice(scope.length + 1)
61
+ : null;
62
+ if (localPath === null)
63
+ continue;
64
+ const result = ig.test(localPath);
65
+ if (result.ignored) {
66
+ ignored = true;
67
+ }
68
+ else if (result.unignored) {
69
+ ignored = false;
70
+ }
71
+ }
72
+ return ignored;
73
+ };
74
+ exports.isPathIgnored = isPathIgnored;
package/dist/render.js ADDED
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderFiles = exports.renderHierarchy = void 0;
4
+ const renderers_1 = require("./renderers");
5
+ const renderHierarchy = (node, options, visiblePaths, currentDepth = 0) => {
6
+ if (visiblePaths &&
7
+ node.relativePath &&
8
+ !visiblePaths.has(node.relativePath)) {
9
+ return null;
10
+ }
11
+ const { indentChar = ' ' } = options;
12
+ const indent = indentChar.repeat(currentDepth);
13
+ if (node.type === 'file') {
14
+ return `${indent}${node.name}`;
15
+ }
16
+ let result = `${indent}${node.name}/`;
17
+ if (node.children.length === 0) {
18
+ return result;
19
+ }
20
+ const childrenOutput = node.children
21
+ .map((child) => (0, exports.renderHierarchy)(child, options, visiblePaths, currentDepth + 1))
22
+ .filter((output) => output !== null)
23
+ .join('\n');
24
+ if (currentDepth === 0) {
25
+ return `\`\`\`
26
+ <directory>
27
+ ${result}
28
+ ${childrenOutput}
29
+ </directory>
30
+ \`\`\`\n\n`;
31
+ }
32
+ else {
33
+ return `${result}\n${childrenOutput}`;
34
+ }
35
+ };
36
+ exports.renderHierarchy = renderHierarchy;
37
+ const renderFiles = (rootPath, files, options) => {
38
+ return files
39
+ .map((file) => renderFileBlock(rootPath, file, options))
40
+ .join('\n\n');
41
+ };
42
+ exports.renderFiles = renderFiles;
43
+ const renderFileBlock = (rootPath, file, options) => {
44
+ const renderer = getRenderer(file);
45
+ const rendered = renderer(rootPath, file, options);
46
+ if (options.verbose) {
47
+ console.warn({ path: file.relativePath, length: rendered.length });
48
+ }
49
+ return `\`\`\`
50
+ <file path="${file.relativePath}">
51
+ ${rendered}
52
+ </file>
53
+ \`\`\``;
54
+ };
55
+ const getRenderer = (file) => {
56
+ for (const { matcher, renderer } of renderers_1.RENDER_RULES) {
57
+ if (matcher(file.name)) {
58
+ return renderer;
59
+ }
60
+ }
61
+ return renderers_1.defaultRenderer;
62
+ };
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.RENDER_RULES = exports.defaultRenderer = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const constants_1 = require("./constants");
40
+ const readFirstNLines = (filePath, n) => {
41
+ const fd = fs.openSync(filePath, 'r');
42
+ const bufferSize = 64 * 1024;
43
+ const buffer = Buffer.alloc(bufferSize);
44
+ const lines = [];
45
+ let leftover = '';
46
+ let hasMore = false;
47
+ try {
48
+ while (lines.length < n) {
49
+ const bytesRead = fs.readSync(fd, buffer, 0, bufferSize, null);
50
+ if (bytesRead === 0)
51
+ break; // EOF
52
+ const chunk = leftover + buffer.toString('utf-8', 0, bytesRead);
53
+ const chunkLines = chunk.split('\n');
54
+ leftover = chunkLines.pop() || '';
55
+ for (const line of chunkLines) {
56
+ if (lines.length < n) {
57
+ lines.push(line);
58
+ }
59
+ else {
60
+ hasMore = true;
61
+ break;
62
+ }
63
+ }
64
+ if (hasMore)
65
+ break;
66
+ }
67
+ if (!hasMore && lines.length >= n) {
68
+ if (leftover !== '') {
69
+ hasMore = true;
70
+ }
71
+ else {
72
+ hasMore = fs.readSync(fd, buffer, 0, 1, null) > 0;
73
+ }
74
+ }
75
+ return { lines, hasMore };
76
+ }
77
+ finally {
78
+ fs.closeSync(fd);
79
+ }
80
+ };
81
+ const numberLines = (lines) => {
82
+ return lines
83
+ .map((line, index) => {
84
+ const lineNumber = (index + 1).toString().padStart(6, ' ');
85
+ return `${lineNumber}\t${line}`;
86
+ })
87
+ .join('\n');
88
+ };
89
+ const defaultRenderer = (rootPath, file, options) => {
90
+ const fullPath = path.join(rootPath, file.relativePath);
91
+ const stats = fs.statSync(fullPath);
92
+ if (stats.size > constants_1.MAX_FILE_SIZE_KB * 1024) {
93
+ return `(File contents excluded: size ${(stats.size / 1024).toFixed(2)}KB exceeds ${constants_1.MAX_FILE_SIZE_KB}KB limit)`;
94
+ }
95
+ const content = fs.readFileSync(fullPath, 'utf-8');
96
+ if (options.lineNumbers) {
97
+ const lines = content.split('\n');
98
+ return numberLines(lines);
99
+ }
100
+ return content;
101
+ };
102
+ exports.defaultRenderer = defaultRenderer;
103
+ exports.RENDER_RULES = [
104
+ // csv
105
+ {
106
+ matcher: (name) => ['.csv'].some((ext) => name.toLowerCase().endsWith(ext)),
107
+ renderer: (rootPath, file, options) => {
108
+ const fullPath = path.join(rootPath, file.relativePath);
109
+ const { lines, hasMore } = readFirstNLines(fullPath, 10);
110
+ const preview = options.lineNumbers
111
+ ? numberLines(lines)
112
+ : lines.join('\n');
113
+ return hasMore ? `${preview}\n... (more rows)` : preview;
114
+ },
115
+ },
116
+ // excel
117
+ {
118
+ matcher: (name) => ['.xls', '.xlsx'].some((ext) => name.toLowerCase().endsWith(ext)),
119
+ renderer: (rootPath, file, options) => '(Contents excluded)',
120
+ },
121
+ // media
122
+ {
123
+ matcher: (name) => ['.ico', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.mp4'].some((ext) => name.toLowerCase().endsWith(ext)),
124
+ renderer: (rootPath, file, options) => '(Contents excluded)',
125
+ },
126
+ // misc
127
+ {
128
+ matcher: (name) => ['.pdf', '.zip'].some((ext) => name.toLowerCase().endsWith(ext)),
129
+ renderer: (rootPath, file, options) => '(Contents excluded)',
130
+ },
131
+ ];
package/dist/select.js ADDED
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getVisiblePaths = exports.selectFiles = void 0;
4
+ const minimatch_1 = require("minimatch");
5
+ const selectFiles = (node, globPatterns, options) => {
6
+ const allFiles = listAllFiles(node);
7
+ const selectedFiles = allFiles.filter((file) => {
8
+ let included = false;
9
+ for (const pattern of globPatterns) {
10
+ if (pattern.startsWith('!')) {
11
+ if ((0, minimatch_1.minimatch)(file.relativePath, pattern.slice(1), { dot: true })) {
12
+ included = false;
13
+ }
14
+ }
15
+ else {
16
+ if ((0, minimatch_1.minimatch)(file.relativePath, pattern, { dot: true })) {
17
+ included = true;
18
+ }
19
+ }
20
+ }
21
+ return included;
22
+ });
23
+ const visiblePaths = (0, exports.getVisiblePaths)(selectedFiles);
24
+ if (options.verbose) {
25
+ if (selectedFiles.length === 0) {
26
+ console.warn(`No file(s) found that satisfy these patterns: ${globPatterns.join(', ')}`);
27
+ }
28
+ else {
29
+ console.warn(`${selectedFiles.length} file(s) found satisfying these patterns: ${globPatterns.join(', ')}`);
30
+ console.warn({ visiblePaths });
31
+ }
32
+ }
33
+ return { selectedFiles, visiblePaths };
34
+ };
35
+ exports.selectFiles = selectFiles;
36
+ const listAllFiles = (node) => {
37
+ return node.children.flatMap((child) => child.type === 'file' ? [child] : listAllFiles(child));
38
+ };
39
+ const getVisiblePaths = (selectedFiles) => {
40
+ const visible = new Set();
41
+ for (const file of selectedFiles) {
42
+ visible.add(file.relativePath);
43
+ const parts = file.relativePath.split('/');
44
+ let current = '';
45
+ for (let i = 0; i < parts.length - 1; i++) {
46
+ current = current ? `${current}/${parts[i]}` : parts[i];
47
+ visible.add(current);
48
+ }
49
+ }
50
+ return visible;
51
+ };
52
+ exports.getVisiblePaths = getVisiblePaths;
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "llmview",
3
+ "version": "0.1.0",
4
+ "bin": {
5
+ "llmview": "dist/cli.js"
6
+ },
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "prepublishOnly": "npm run build",
10
+ "test": "node --test --experimental-strip-types test/**/*.test.ts"
11
+ },
12
+ "keywords": [],
13
+ "author": "",
14
+ "license": "ISC",
15
+ "description": "Generate LLM context from codebases using gitignore-style patterns",
16
+ "dependencies": {
17
+ "ignore": "^7.0.5",
18
+ "minimatch": "^10.0.1"
19
+ },
20
+ "devDependencies": {
21
+ "prettier": "^3.8.1",
22
+ "ts-node": "^10.9.2",
23
+ "typescript": "^5.8.2"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/noahtren/llmview.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/noahtren/llmview/issues"
35
+ }
36
+ }