fln 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.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +330 -0
  3. package/dist/api/fln.d.ts +3 -0
  4. package/dist/api/fln.d.ts.map +1 -0
  5. package/dist/api/fln.js +71 -0
  6. package/dist/api/index.d.ts +3 -0
  7. package/dist/api/index.d.ts.map +1 -0
  8. package/dist/api/index.js +2 -0
  9. package/dist/api/types.d.ts +34 -0
  10. package/dist/api/types.d.ts.map +1 -0
  11. package/dist/api/types.js +1 -0
  12. package/dist/cli/commandLine.d.ts +2 -0
  13. package/dist/cli/commandLine.d.ts.map +1 -0
  14. package/dist/cli/commandLine.js +120 -0
  15. package/dist/cli/help.d.ts +2 -0
  16. package/dist/cli/help.d.ts.map +1 -0
  17. package/dist/cli/help.js +40 -0
  18. package/dist/cli/index.d.ts +2 -0
  19. package/dist/cli/index.d.ts.map +1 -0
  20. package/dist/cli/index.js +1 -0
  21. package/dist/cli/main.d.ts +3 -0
  22. package/dist/cli/main.d.ts.map +1 -0
  23. package/dist/cli/main.js +9 -0
  24. package/dist/cli/output/components/breakdown.d.ts +2 -0
  25. package/dist/cli/output/components/breakdown.d.ts.map +1 -0
  26. package/dist/cli/output/components/breakdown.js +21 -0
  27. package/dist/cli/output/components/errors.d.ts +6 -0
  28. package/dist/cli/output/components/errors.d.ts.map +1 -0
  29. package/dist/cli/output/components/errors.js +13 -0
  30. package/dist/cli/output/components/progressBar.d.ts +7 -0
  31. package/dist/cli/output/components/progressBar.d.ts.map +1 -0
  32. package/dist/cli/output/components/progressBar.js +38 -0
  33. package/dist/cli/output/components/summary.d.ts +8 -0
  34. package/dist/cli/output/components/summary.d.ts.map +1 -0
  35. package/dist/cli/output/components/summary.js +12 -0
  36. package/dist/cli/output/components/warnings.d.ts +6 -0
  37. package/dist/cli/output/components/warnings.d.ts.map +1 -0
  38. package/dist/cli/output/components/warnings.js +11 -0
  39. package/dist/cli/output/formatter.d.ts +5 -0
  40. package/dist/cli/output/formatter.d.ts.map +1 -0
  41. package/dist/cli/output/formatter.js +28 -0
  42. package/dist/cli/output/index.d.ts +8 -0
  43. package/dist/cli/output/index.d.ts.map +1 -0
  44. package/dist/cli/output/index.js +4 -0
  45. package/dist/cli/output/renderer.d.ts +21 -0
  46. package/dist/cli/output/renderer.d.ts.map +1 -0
  47. package/dist/cli/output/renderer.js +121 -0
  48. package/dist/cli/output/styles.d.ts +23 -0
  49. package/dist/cli/output/styles.d.ts.map +1 -0
  50. package/dist/cli/output/styles.js +26 -0
  51. package/dist/config/defaults.d.ts +3 -0
  52. package/dist/config/defaults.d.ts.map +1 -0
  53. package/dist/config/defaults.js +2 -0
  54. package/dist/config/index.d.ts +6 -0
  55. package/dist/config/index.d.ts.map +1 -0
  56. package/dist/config/index.js +5 -0
  57. package/dist/config/loader.d.ts +3 -0
  58. package/dist/config/loader.d.ts.map +1 -0
  59. package/dist/config/loader.js +11 -0
  60. package/dist/config/resolver.d.ts +8 -0
  61. package/dist/config/resolver.d.ts.map +1 -0
  62. package/dist/config/resolver.js +66 -0
  63. package/dist/config/types.d.ts +40 -0
  64. package/dist/config/types.d.ts.map +1 -0
  65. package/dist/config/types.js +1 -0
  66. package/dist/config/utils.d.ts +7 -0
  67. package/dist/config/utils.d.ts.map +1 -0
  68. package/dist/config/utils.js +161 -0
  69. package/dist/core/ignoreMatcher.d.ts +15 -0
  70. package/dist/core/ignoreMatcher.d.ts.map +1 -0
  71. package/dist/core/ignoreMatcher.js +97 -0
  72. package/dist/core/index.d.ts +8 -0
  73. package/dist/core/index.d.ts.map +1 -0
  74. package/dist/core/index.js +7 -0
  75. package/dist/core/renderOutput.d.ts +4 -0
  76. package/dist/core/renderOutput.d.ts.map +1 -0
  77. package/dist/core/renderOutput.js +218 -0
  78. package/dist/core/renderTree.d.ts +3 -0
  79. package/dist/core/renderTree.d.ts.map +1 -0
  80. package/dist/core/renderTree.js +23 -0
  81. package/dist/core/scanTree.d.ts +4 -0
  82. package/dist/core/scanTree.d.ts.map +1 -0
  83. package/dist/core/scanTree.js +348 -0
  84. package/dist/core/size.d.ts +4 -0
  85. package/dist/core/size.d.ts.map +1 -0
  86. package/dist/core/size.js +32 -0
  87. package/dist/core/statsCollector.d.ts +4 -0
  88. package/dist/core/statsCollector.d.ts.map +1 -0
  89. package/dist/core/statsCollector.js +28 -0
  90. package/dist/core/types.d.ts +53 -0
  91. package/dist/core/types.d.ts.map +1 -0
  92. package/dist/core/types.js +1 -0
  93. package/dist/index.d.ts +4 -0
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +3 -0
  96. package/dist/infra/countTokens.d.ts +2 -0
  97. package/dist/infra/countTokens.d.ts.map +1 -0
  98. package/dist/infra/countTokens.js +186 -0
  99. package/dist/infra/datetime.d.ts +3 -0
  100. package/dist/infra/datetime.d.ts.map +1 -0
  101. package/dist/infra/datetime.js +15 -0
  102. package/dist/infra/index.d.ts +7 -0
  103. package/dist/infra/index.d.ts.map +1 -0
  104. package/dist/infra/index.js +6 -0
  105. package/dist/infra/logger.d.ts +19 -0
  106. package/dist/infra/logger.d.ts.map +1 -0
  107. package/dist/infra/logger.js +99 -0
  108. package/dist/infra/outputWriter.d.ts +15 -0
  109. package/dist/infra/outputWriter.d.ts.map +1 -0
  110. package/dist/infra/outputWriter.js +35 -0
  111. package/dist/infra/terminal.d.ts +91 -0
  112. package/dist/infra/terminal.d.ts.map +1 -0
  113. package/dist/infra/terminal.js +189 -0
  114. package/dist/infra/usageTracker.d.ts +3 -0
  115. package/dist/infra/usageTracker.d.ts.map +1 -0
  116. package/dist/infra/usageTracker.js +39 -0
  117. package/dist/version.d.ts +2 -0
  118. package/dist/version.d.ts.map +1 -0
  119. package/dist/version.js +1 -0
  120. package/package.json +79 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eugene Nesvetaev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,330 @@
1
+ # 🥞 fln
2
+
3
+ [![CI](https://github.com/nesvet/fln/actions/workflows/ci.yaml/badge.svg)](https://github.com/nesvet/fln/actions/workflows/ci.yaml)
4
+ [![npm](https://img.shields.io/npm/v/fln)](https://www.npmjs.com/package/fln)
5
+ [![license](https://img.shields.io/npm/l/fln)](LICENSE)
6
+
7
+ **Your entire codebase → One AI-ready file.**
8
+
9
+ Stop wrestling with file pickers and attachment limits — feed your whole project to any LLM in one shot.
10
+
11
+ ```bash
12
+ fln .
13
+ ```
14
+
15
+ Or run instantly:
16
+
17
+ ```bash
18
+ npx fln . -o codebase.md
19
+ ```
20
+
21
+ Works with **Claude**, **ChatGPT**, **Gemini**, **Grok**, **Cursor**, **Copilot**, and *any* AI tool.
22
+
23
+ **`fln`** (short for *flatten*) is **language-agnostic** by design: TypeScript, Python, Java, Go, Rust, Bash, SQL, mixed monorepos — it treats everything as plain text, detects project metadata from common manifests (`package.json`, `pyproject.toml`, `go.mod`, `Cargo.toml`, `CMakeLists.txt`, `vcpkg.json`), respects `.gitignore`, and skips binaries by default.
24
+
25
+ ## Why fln exists
26
+
27
+ If you use LLMs for real projects, you’ve hit these limits:
28
+
29
+ - **Context windows** — large projects don’t fit.
30
+ - **Upload friction** — selecting dozens of files for every session.
31
+ - **Partial understanding** — AI sees fragments, not the architecture.
32
+ - **Manual prep** — repeating the same setup context again and again.
33
+
34
+ **`fln` removes that overhead.**
35
+ It turns your project into a single, structured snapshot that LLMs can actually reason about.
36
+
37
+ ## What fln enables
38
+
39
+ **→ Full-context refactoring**
40
+ Ask architectural questions that are impossible file-by-file:
41
+ > “Where is the real coupling here?”
42
+ > “What should be split into modules?”
43
+
44
+ **→ Instant onboarding**
45
+ One markdown file instead of “start by opening these 12 folders”. Perfect for reading code on a tablet or onboarding new developers without an IDE.
46
+
47
+ **→ Project-level code reviews**
48
+ Let AI detect patterns, inconsistencies, and risks across the entire codebase.
49
+
50
+ **→ Auditable Snapshots**
51
+ Create a single, clean artifact of your codebase state for security reviews, compliance audits, or legal records without granting full repo access.
52
+
53
+ **→ Dataset Preparation**
54
+ Generate clean, formatted data for RAG pipelines and fine-tuning custom models.
55
+
56
+ **→ LLM-friendly diffs**
57
+ Flatten → commit → flatten again. See how the *whole project* changed structurally.
58
+
59
+ ## Compatible with your AI workflow
60
+
61
+ - **Claude** — ideal for large architectural prompts (200K+ tokens).
62
+ - **Gemini** — push massive codebases into 1M token windows.
63
+ - **ChatGPT** — single-shot analysis without attachments.
64
+ - **Cursor / Windsurf** — reference the full project in prompts.
65
+ - **GitHub Copilot** — better context → better suggestions.
66
+ - **Local LLMs** — datasets for RAG and fine-tuning.
67
+
68
+ ## Built for real projects
69
+
70
+ - ⚡ **Fast parallel scanning** — thousands of files in seconds.
71
+ - 🎯 **Smart filtering** — respects `.gitignore`, excludes binaries, configurable size limits.
72
+ - 📁 **Intentional file order** — entry points and configs first, not alphabetical noise.
73
+ - 🔄 **Auto-detection** — skips files previously generated by `fln`.
74
+ - 📐 **Deterministic output** — same input → same snapshot.
75
+ - 🧠 **Project metadata detection** — name & version from ecosystem-native manifests.
76
+ - 🛠️ **Developer-friendly** — `Markdown` for humans, `JSON` for tooling, `--dry-run` mode for safety.
77
+ - 🔒 **No surprises** — runs locally, no data leaves your machine.
78
+
79
+ Zero dependencies on external services. Zero tracking. Just a tool that does its job.
80
+
81
+ ## Install
82
+
83
+ ```bash
84
+ # npm
85
+ npm install -g fln
86
+
87
+ # one-liner (macOS/Linux)
88
+ curl -fsSL "https://raw.githubusercontent.com/nesvet/fln/main/install.sh" | sh
89
+
90
+ # one-liner (Windows)
91
+ irm "https://raw.githubusercontent.com/nesvet/fln/main/install.ps1" | iex
92
+
93
+ # or just run without installing
94
+ npx fln . -o codebase.md
95
+ ```
96
+
97
+ <details>
98
+ <summary>More installation options</summary>
99
+
100
+ ### One-line installer options (macOS/Linux)
101
+
102
+ Pin a version or custom install directory:
103
+
104
+ ```bash
105
+ curl -fsSL "https://raw.githubusercontent.com/nesvet/fln/main/install.sh" | FLN_VERSION="<version>" INSTALL_DIR="$HOME/.local/bin" sh
106
+ ```
107
+
108
+ ### One-line installer options (Windows PowerShell)
109
+
110
+ ```powershell
111
+ $env:FLN_VERSION = "<version>"
112
+ $env:INSTALL_DIR = "$env:LOCALAPPDATA\\fln\\bin"
113
+ irm "https://raw.githubusercontent.com/nesvet/fln/main/install.ps1" | iex
114
+ ```
115
+
116
+ ### Manual download (GitHub Releases)
117
+
118
+ ```bash
119
+ curl -L "https://github.com/nesvet/fln/releases/latest/download/fln-macos-x64.tar.gz" | tar -xz -C /usr/local/bin
120
+ chmod +x /usr/local/bin/fln
121
+ fln --help
122
+ ```
123
+
124
+ </details>
125
+
126
+ ## Usage
127
+
128
+ ```bash
129
+ fln [directory] [options]
130
+ ```
131
+
132
+ Examples:
133
+
134
+ ```bash
135
+ # Flatten entire project
136
+ fln .
137
+
138
+ # Exclude tests and fixtures
139
+ fln src -e "**/*.test.ts" -e "fixtures/"
140
+
141
+ # Force include a file (even if ignored)
142
+ fln . -i "dist/output.md"
143
+
144
+ # Generate JSON for tooling
145
+ fln . --no-contents --format json
146
+
147
+ # Preview without writing
148
+ fln . --dry-run
149
+ ```
150
+
151
+ <details>
152
+ <summary>All CLI options</summary>
153
+
154
+ - `-o, --output <path>` Output file or directory
155
+ - `-e, --exclude <glob>` Exclude patterns (repeatable)
156
+ - `-i, --include <glob>` Force include patterns
157
+ - `--include-hidden` Include hidden files and directories
158
+ - `--no-gitignore` Ignore `.gitignore`
159
+ - `--max-size <size>` Max file size (`10mb`, `512kb`)
160
+ - `--max-total-size <size>` Max total included size
161
+ - `--no-contents` Exclude file contents
162
+ - `--no-tree` Exclude directory tree
163
+ - `--format <md|json>` Output format
164
+ - `--dry-run` Scan without writing output
165
+ - `--follow-symlinks` Follow symlinks
166
+ - `--no-ansi` Disable ANSI colors
167
+ - `--no-sponsor-message` Hide support message (also: `FLN_NO_SPONSOR=1`)
168
+ - `--generated-date <date>` Use this date in the "Generated" header (format: `YYYY-MM-DD HH:mm`)
169
+ - `--banner <text>` Add text at the beginning
170
+ - `--footer <text>` Add text at the end of the output
171
+ - `-q, --quiet` Minimal output
172
+ - `-V, --verbose` Verbose output with breakdown
173
+ - `--debug` Debug output with file list
174
+ - `-v, --version` Show version
175
+ - `-h, --help` Show help
176
+
177
+ </details>
178
+
179
+ ## CI/CD & Automation
180
+
181
+ Integrate `fln` into your pipeline to keep your codebase “AI-ready” automatically.
182
+
183
+ ### GitHub Actions: Auto-generate Snapshots
184
+
185
+ Generate a fresh `codebase.md` artifact on every push. Download it anytime to chat with LLMs about the *exact* state of your main branch or a specific PR without manual scanning.
186
+
187
+ Create `.github/workflows/codebase-snapshot.yaml`:
188
+
189
+ ```yaml
190
+ name: Snapshot Codebase
191
+
192
+ on:
193
+ push:
194
+ branches: [ "main" ]
195
+ pull_request:
196
+
197
+ jobs:
198
+ snapshot:
199
+ runs-on: ubuntu-latest
200
+ permissions:
201
+ contents: read
202
+ steps:
203
+ - uses: actions/checkout@v6
204
+
205
+ - name: Generate Snapshot
206
+ # Generates codebase.md without installing fln globally
207
+ run: npx fln . -o codebase.md --no-ansi
208
+
209
+ - name: Upload Artifact
210
+ uses: actions/upload-artifact@v6
211
+ with:
212
+ name: codebase-snapshot
213
+ path: codebase.md
214
+ retention-days: 7
215
+ ```
216
+
217
+ ### Git Hooks: Pre-commit Context Guard
218
+
219
+ Prevent accidental “context bloat” (e.g., committing large datasets or wrong lockfiles) by failing commits if the flattened codebase exceeds a specific size. This ensures your project always fits within LLM context windows.
220
+
221
+ Add to your pre-commit hook (e.g., via `husky` or `lint-staged`):
222
+
223
+ ```bash
224
+ # Fails the commit if the flattened codebase exceeds 5MB (configurable)
225
+ # --dry-run ensures no files are written to disk
226
+ npx fln . --dry-run --max-total-size 5mb
227
+ ```
228
+
229
+ ## JavaScript API
230
+
231
+ ```typescript
232
+ import { fln } from "fln";
233
+
234
+ const result = await fln({
235
+ rootDirectory: "./src",
236
+ outputFile: "output.md",
237
+ excludePatterns: [ "*.test.ts", "fixtures/" ],
238
+ format: "md",
239
+ onProgress: (current, total) => {
240
+ console.log(`Progress: ${current}/${total}`);
241
+ }
242
+ });
243
+
244
+ console.log(`Processed ${result.files} files`);
245
+ console.log(`Output: ${result.outputPath}`);
246
+ console.log(`Tokens: ${result.outputTokenCount}`);
247
+ ```
248
+
249
+ All CLI options are available via `FlnOptions`.
250
+
251
+
252
+ ## Advanced
253
+
254
+ <details>
255
+ <summary>Configuration file (.fln.json)</summary>
256
+
257
+ ```json
258
+ {
259
+ "outputFile": "output.md",
260
+ "excludePatterns": [
261
+ "dist/",
262
+ "**/*.snap"
263
+ ],
264
+ "includePatterns": [],
265
+ "includeHidden": false,
266
+ "useGitignore": true,
267
+ "maximumFileSizeBytes": "10mb",
268
+ "maximumTotalSizeBytes": "0",
269
+ "includeTree": true,
270
+ "includeContents": true,
271
+ "format": "md",
272
+ "followSymlinks": false,
273
+ "logLevel": "normal",
274
+ "generatedDate": "2026-02-09 12:00",
275
+ "banner": "This is a snapshot of the codebase.",
276
+ "footer": "End of snapshot."
277
+ }
278
+ ```
279
+
280
+ </details>
281
+
282
+ <details>
283
+ <summary>Output naming & formats</summary>
284
+
285
+ - Uses project name + version if available (`package.json`, `pyproject.toml`, `Cargo.toml`, `vcpkg.json`, `go.mod`, or `CMakeLists.txt`)
286
+ - `md` includes tree + contents
287
+ - `json` includes `rootDirectory`, `tree`, `stats`
288
+
289
+ </details>
290
+
291
+ <details>
292
+ <summary>Runtime compatibility</summary>
293
+
294
+ **Node.js**
295
+ - Requires Node.js `>=18.3`
296
+ - ESM-only package (`"type": "module"`)
297
+ - CLI works via `npm i -g fln` or `npx fln`
298
+
299
+ **Bun**
300
+ - Requires Bun `>=1.0.0`
301
+ - CLI works via `bun install -g fln` or `bunx fln`
302
+
303
+ </details>
304
+
305
+ ## Preview
306
+
307
+ Full real outputs are provided below. Each example is a compact project in [`examples/`](examples/). `fln` outputs the directory tree and file contents with **entry points and configs first** (intentional file order):
308
+
309
+ - [TypeScript](examples/ts-app.md)
310
+ - [Python](examples/python-app.md)
311
+ - [Java](examples/java-app.md)
312
+ - [Go](examples/go-app.md)
313
+ - [Rust](examples/rust-app.md)
314
+
315
+ ## Support this project
316
+
317
+ **fln is free, open-source, and maintained by one developer.**
318
+
319
+ If it saves you time or improves your AI workflow:
320
+ - ⭐️ Star the repo — it genuinely helps discoverability
321
+ - 💙 Support on [Patreon](https://www.patreon.com/nesvet) — priority features & long-term maintenance
322
+
323
+ ## Contributing
324
+
325
+ PRs and issues are welcome.
326
+ See [`CONTRIBUTING.md`](CONTRIBUTING.md) for setup and guidelines.
327
+
328
+ ## License
329
+
330
+ MIT
@@ -0,0 +1,3 @@
1
+ import type { FlnOptions, FlnResult } from "./types";
2
+ export declare function fln(options?: FlnOptions): Promise<FlnResult>;
3
+ //# sourceMappingURL=fln.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fln.d.ts","sourceRoot":"","sources":["../../src/api/fln.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAuBrD,wBAAsB,GAAG,CAAC,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAsFtE"}
@@ -0,0 +1,71 @@
1
+ import { relative, resolve, sep } from "node:path";
2
+ import { parseByteSize, scanTree, writeOutput } from "$core";
3
+ import { createLogger } from "$infra";
4
+ import { defaultConfigFileName, getProjectMetadata, loadConfigFile, normalizeConfigFile, resolveConfig, resolveOutputPath } from "../config";
5
+ export async function fln(options = {}) {
6
+ const rootDirectory = resolve(options.rootDirectory ?? process.cwd());
7
+ const projectMetadata = await getProjectMetadata(rootDirectory);
8
+ const configFilePath = resolve(rootDirectory, defaultConfigFileName);
9
+ const fileConfig = normalizeConfigFile(await loadConfigFile(configFilePath));
10
+ const format = options.format ?? fileConfig.format ?? "md";
11
+ const outputValue = options.outputFile ?? fileConfig.outputFile;
12
+ const outputFile = await resolveOutputPath(outputValue ? resolve(outputValue) : undefined, rootDirectory, format);
13
+ const userConfig = {
14
+ outputFile,
15
+ excludePatterns: options.excludePatterns,
16
+ includePatterns: options.includePatterns,
17
+ includeHidden: options.includeHidden,
18
+ useGitignore: options.useGitignore,
19
+ maximumFileSizeBytes: typeof options.maximumFileSizeBytes === "string" ?
20
+ parseByteSize(options.maximumFileSizeBytes) :
21
+ options.maximumFileSizeBytes,
22
+ maximumTotalSizeBytes: typeof options.maximumTotalSizeBytes === "string" ?
23
+ parseByteSize(options.maximumTotalSizeBytes) :
24
+ options.maximumTotalSizeBytes,
25
+ includeContents: options.includeContents,
26
+ includeTree: options.includeTree,
27
+ format,
28
+ followSymlinks: options.followSymlinks,
29
+ useAnsi: false,
30
+ logLevel: options.logLevel ?? "silent",
31
+ generatedDate: options.generatedDate,
32
+ banner: options.banner,
33
+ footer: options.footer
34
+ };
35
+ const config = resolveConfig(rootDirectory, fileConfig, userConfig);
36
+ const outputRelativePath = relative(rootDirectory, config.outputFile);
37
+ const outputRelativeNormalized = outputRelativePath.split(sep).join("/");
38
+ if (outputRelativeNormalized !== "" && !outputRelativeNormalized.startsWith("../") && outputRelativeNormalized !== "..")
39
+ config.excludedPaths = [outputRelativeNormalized];
40
+ if (!config.includeContents) {
41
+ config.maximumFileSizeBytes = Number.MAX_SAFE_INTEGER;
42
+ config.maximumTotalSizeBytes = 0;
43
+ }
44
+ if (config.maximumFileSizeBytes <= 0)
45
+ throw new Error("Max file size must be greater than 0.");
46
+ if (config.maximumTotalSizeBytes < 0)
47
+ throw new Error("Max total size must be 0 or greater.");
48
+ const logger = createLogger({
49
+ useAnsi: config.useAnsi,
50
+ logLevel: config.logLevel
51
+ });
52
+ const result = await scanTree({
53
+ projectName: projectMetadata.name,
54
+ ...config,
55
+ onProgress: options.onProgress
56
+ }, logger);
57
+ await writeOutput(result, config);
58
+ return {
59
+ projectName: result.projectName,
60
+ files: result.stats.files,
61
+ directories: result.stats.directories,
62
+ binary: result.stats.binary,
63
+ skipped: result.stats.skipped,
64
+ errors: result.stats.errors,
65
+ totalSizeBytes: result.stats.totalSizeBytes,
66
+ outputSizeBytes: result.stats.outputSizeBytes,
67
+ outputTokenCount: result.stats.outputTokenCount,
68
+ outputPath: config.outputFile,
69
+ _root: result.root
70
+ };
71
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./fln";
2
+ export * from "./types";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from "./fln";
2
+ export * from "./types";
@@ -0,0 +1,34 @@
1
+ import type { LogLevel, ProgressCallback } from "$core";
2
+ export type FlnOptions = {
3
+ rootDirectory?: string;
4
+ outputFile?: string;
5
+ excludePatterns?: string[];
6
+ includePatterns?: string[];
7
+ includeHidden?: boolean;
8
+ useGitignore?: boolean;
9
+ maximumFileSizeBytes?: number | string;
10
+ maximumTotalSizeBytes?: number | string;
11
+ includeContents?: boolean;
12
+ includeTree?: boolean;
13
+ format?: "json" | "md";
14
+ followSymlinks?: boolean;
15
+ generatedDate?: string;
16
+ banner?: string;
17
+ footer?: string;
18
+ onProgress?: ProgressCallback;
19
+ logLevel?: LogLevel;
20
+ };
21
+ export type FlnResult = {
22
+ projectName: string;
23
+ files: number;
24
+ directories: number;
25
+ binary: number;
26
+ skipped: number;
27
+ errors: number;
28
+ totalSizeBytes: number;
29
+ outputSizeBytes: number;
30
+ outputTokenCount: number;
31
+ outputPath: string;
32
+ _root?: unknown;
33
+ };
34
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAMxD,MAAM,MAAM,UAAU,GAAG;IAKxB,aAAa,CAAC,EAAE,MAAM,CAAC;IAMvB,UAAU,CAAC,EAAE,MAAM,CAAC;IAMpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAM3B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAM3B,aAAa,CAAC,EAAE,OAAO,CAAC;IAMxB,YAAY,CAAC,EAAE,OAAO,CAAC;IAMvB,oBAAoB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAMvC,qBAAqB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAMxC,eAAe,CAAC,EAAE,OAAO,CAAC;IAM1B,WAAW,CAAC,EAAE,OAAO,CAAC;IAMtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAMvB,cAAc,CAAC,EAAE,OAAO,CAAC;IAKzB,aAAa,CAAC,EAAE,MAAM,CAAC;IAKvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAKhB,MAAM,CAAC,EAAE,MAAM,CAAC;IAOhB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAM9B,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACpB,CAAC;AAKF,MAAM,MAAM,SAAS,GAAG;IAIvB,WAAW,EAAE,MAAM,CAAC;IAKpB,KAAK,EAAE,MAAM,CAAC;IAKd,WAAW,EAAE,MAAM,CAAC;IAKpB,MAAM,EAAE,MAAM,CAAC;IAKf,OAAO,EAAE,MAAM,CAAC;IAKhB,MAAM,EAAE,MAAM,CAAC;IAKf,cAAc,EAAE,MAAM,CAAC;IAKvB,eAAe,EAAE,MAAM,CAAC;IAKxB,gBAAgB,EAAE,MAAM,CAAC;IAKzB,UAAU,EAAE,MAAM,CAAC;IAMnB,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare function runCommandLine(): Promise<void>;
2
+ //# sourceMappingURL=commandLine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commandLine.d.ts","sourceRoot":"","sources":["../../src/cli/commandLine.ts"],"names":[],"mappings":"AAwCA,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAkHpD"}
@@ -0,0 +1,120 @@
1
+ import { resolve } from "node:path";
2
+ import { parseArgs } from "node:util";
3
+ import { fln } from "$api";
4
+ import { collectExtensionStats, collectProcessedFiles, parseByteSize } from "$core";
5
+ import { getTerminalInfo, incrementUsageCount, shouldShowSponsorMessage, shouldUseColors } from "$infra";
6
+ import { VERSION } from "$version";
7
+ import { formatHelpMessage } from "./help";
8
+ import { OutputRenderer } from "./output";
9
+ function isCI() {
10
+ return Boolean(process.env.CI ||
11
+ process.env.CONTINUOUS_INTEGRATION ||
12
+ process.env.BUILD_NUMBER ||
13
+ process.env.GITHUB_ACTIONS ||
14
+ process.env.GITLAB_CI ||
15
+ process.env.CIRCLECI);
16
+ }
17
+ function shouldShowSponsor(runCount, noSponsorFlag) {
18
+ return (!noSponsorFlag &&
19
+ !isCI() &&
20
+ process.env.FLN_NO_SPONSOR !== "1" &&
21
+ shouldShowSponsorMessage(runCount));
22
+ }
23
+ export async function runCommandLine() {
24
+ const { values, positionals } = parseArgs({
25
+ options: {
26
+ output: { type: "string", short: "o" },
27
+ exclude: { type: "string", short: "e", multiple: true },
28
+ include: { type: "string", short: "i", multiple: true },
29
+ "include-hidden": { type: "boolean" },
30
+ "no-gitignore": { type: "boolean" },
31
+ "max-size": { type: "string" },
32
+ "max-total-size": { type: "string" },
33
+ "no-contents": { type: "boolean" },
34
+ "no-tree": { type: "boolean" },
35
+ format: { type: "string" },
36
+ "dry-run": { type: "boolean" },
37
+ quiet: { type: "boolean", short: "q" },
38
+ verbose: { type: "boolean", short: "V" },
39
+ debug: { type: "boolean" },
40
+ "no-ansi": { type: "boolean" },
41
+ "follow-symlinks": { type: "boolean" },
42
+ "no-sponsor-message": { type: "boolean" },
43
+ "generated-date": { type: "string" },
44
+ banner: { type: "string" },
45
+ footer: { type: "string" },
46
+ version: { type: "boolean", short: "v" },
47
+ help: { type: "boolean", short: "h" }
48
+ },
49
+ strict: true,
50
+ allowPositionals: true
51
+ });
52
+ if (values.version) {
53
+ console.info(VERSION);
54
+ process.exit(0);
55
+ }
56
+ if (values.help) {
57
+ const { supportsAnsi } = getTerminalInfo();
58
+ console.info(formatHelpMessage(supportsAnsi));
59
+ process.exit(0);
60
+ }
61
+ if (values.quiet && values.verbose)
62
+ throw new Error("Cannot use --quiet and --verbose together.");
63
+ if (values.quiet && values.debug)
64
+ throw new Error("Cannot use --quiet and --debug together.");
65
+ if (values.verbose && values.debug)
66
+ throw new Error("Cannot use --verbose and --debug together.");
67
+ const runCount = await incrementUsageCount();
68
+ const rootDirectory = resolve(process.cwd(), positionals[0] || ".");
69
+ const isDryRun = values["dry-run"] ?? false;
70
+ const logLevel = values.quiet ? "silent" : values.debug ? "debug" : values.verbose ? "verbose" : "normal";
71
+ const useAnsi = shouldUseColors() && !values["no-ansi"];
72
+ const renderer = new OutputRenderer({ logLevel, useAnsi });
73
+ const progress = renderer.createProgressBar(100);
74
+ const startTime = Date.now();
75
+ const result = await fln({
76
+ rootDirectory,
77
+ outputFile: isDryRun ?
78
+ (process.platform === "win32" ? "nul" : "/dev/null") :
79
+ (values.output ? resolve(values.output) : undefined),
80
+ excludePatterns: values.exclude,
81
+ includePatterns: values.include,
82
+ includeHidden: values["include-hidden"],
83
+ useGitignore: values["no-gitignore"] ? false : undefined,
84
+ maximumFileSizeBytes: values["max-size"] ? parseByteSize(values["max-size"]) : undefined,
85
+ maximumTotalSizeBytes: values["max-total-size"] ? parseByteSize(values["max-total-size"]) : undefined,
86
+ includeContents: values["no-contents"] ? false : undefined,
87
+ includeTree: values["no-tree"] ? false : undefined,
88
+ format: values.format,
89
+ followSymlinks: values["follow-symlinks"],
90
+ generatedDate: values["generated-date"],
91
+ banner: values.banner,
92
+ footer: values.footer,
93
+ onProgress: () => {
94
+ progress.increment();
95
+ },
96
+ logLevel
97
+ });
98
+ const elapsedMs = Date.now() - startTime;
99
+ progress.clear();
100
+ if (isDryRun && logLevel !== "silent")
101
+ console.info("Dry run mode — output was not written");
102
+ const breakdown = (logLevel === "verbose" || logLevel === "debug") && result._root ?
103
+ collectExtensionStats(result._root) :
104
+ undefined;
105
+ const processedFiles = logLevel === "debug" && result._root ?
106
+ collectProcessedFiles(result._root) :
107
+ undefined;
108
+ renderer.renderSuccess({
109
+ outputPath: result.outputPath,
110
+ result,
111
+ elapsedMs,
112
+ breakdown,
113
+ processedFiles
114
+ });
115
+ if (!isDryRun && logLevel !== "silent" && shouldShowSponsor(runCount, Boolean(values["no-sponsor-message"]))) {
116
+ console.info("");
117
+ console.info("💙 Support fln development: https://patreon.com/nesvet");
118
+ console.info("");
119
+ }
120
+ }
@@ -0,0 +1,2 @@
1
+ export declare function formatHelpMessage(useAnsi: boolean): string;
2
+ //# sourceMappingURL=help.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/cli/help.ts"],"names":[],"mappings":"AAGA,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAuC1D"}
@@ -0,0 +1,40 @@
1
+ import { applyColor, colors } from "./output/styles";
2
+ export function formatHelpMessage(useAnsi) {
3
+ const bold = (text) => applyColor(text, colors.bold, useAnsi);
4
+ const dim = (text) => applyColor(text, colors.dim, useAnsi);
5
+ const cyan = (text) => applyColor(text, colors.info, useAnsi);
6
+ const green = (text) => applyColor(text, colors.success, useAnsi);
7
+ return `${bold("fln")} ${dim("—")} Flatten your codebase into a single file for LLMs.
8
+
9
+ ${bold("Usage:")} fln ${cyan("[directory]")} ${dim("[...flags]")}
10
+
11
+ ${bold("Options:")}
12
+ ${cyan("-o, --output")} ${dim("<path>")} Output file or directory path ${dim("(default: <name>-<version>.<ext>)")}
13
+ ${cyan("-e, --exclude")} ${dim("<glob>")} Exclude patterns ${dim("(repeatable)")}
14
+ ${cyan("-i, --include")} ${dim("<glob>")} Force include patterns ${dim("(repeatable)")}
15
+ ${cyan(" --include-hidden")} Include hidden files and directories
16
+ ${cyan(" --no-gitignore")} Ignore .gitignore files
17
+ ${cyan(" --max-size")} ${dim("<size>")} Max file size ${dim("(e.g. 10mb, 512kb)")}
18
+ ${cyan(" --max-total-size")} ${dim("<size>")} Max total included size
19
+ ${cyan(" --no-contents")} Exclude file contents
20
+ ${cyan(" --no-tree")} Exclude directory tree
21
+ ${cyan(" --format")} ${dim("<md|json>")} Output format ${dim("(default: md)")}
22
+ ${cyan(" --dry-run")} Scan and report without writing output
23
+ ${cyan(" --follow-symlinks")} Follow symlinks while scanning
24
+ ${cyan(" --no-ansi")} Disable ANSI colors
25
+ ${cyan(" --no-sponsor-message")} Hide support message ${dim("(also: FLN_NO_SPONSOR=1)")}
26
+ ${cyan(" --generated-date")} ${dim("<date>")} Use this date in the "Generated" header ${dim("(YYYY-MM-DD HH:mm)")}
27
+ ${cyan(" --banner")} ${dim("<text>")} Add text at the beginning of the output
28
+ ${cyan(" --footer")} ${dim("<text>")} Add text at the end of the output
29
+ ${cyan("-q, --quiet")} Minimal output
30
+ ${cyan("-V, --verbose")} Verbose output
31
+ ${cyan(" --debug")} Debug output with file list
32
+ ${cyan("-v, --version")} Show version
33
+ ${cyan("-h, --help")} Show this help message
34
+
35
+ ${bold("Examples:")}
36
+ ${dim("$")} fln . ${cyan("-o")} output.md
37
+ ${dim("$")} fln src ${cyan("-e")} ${green('"*.test.ts"')} ${cyan("-e")} ${green('"fixtures/"')}
38
+ ${dim("$")} fln . ${cyan("--no-contents --format")} json
39
+ `;
40
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./commandLine";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC"}
@@ -0,0 +1 @@
1
+ export * from "./commandLine";