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.
- package/LICENSE +21 -0
- package/README.md +330 -0
- package/dist/api/fln.d.ts +3 -0
- package/dist/api/fln.d.ts.map +1 -0
- package/dist/api/fln.js +71 -0
- package/dist/api/index.d.ts +3 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +2 -0
- package/dist/api/types.d.ts +34 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +1 -0
- package/dist/cli/commandLine.d.ts +2 -0
- package/dist/cli/commandLine.d.ts.map +1 -0
- package/dist/cli/commandLine.js +120 -0
- package/dist/cli/help.d.ts +2 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +40 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/main.d.ts +3 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +9 -0
- package/dist/cli/output/components/breakdown.d.ts +2 -0
- package/dist/cli/output/components/breakdown.d.ts.map +1 -0
- package/dist/cli/output/components/breakdown.js +21 -0
- package/dist/cli/output/components/errors.d.ts +6 -0
- package/dist/cli/output/components/errors.d.ts.map +1 -0
- package/dist/cli/output/components/errors.js +13 -0
- package/dist/cli/output/components/progressBar.d.ts +7 -0
- package/dist/cli/output/components/progressBar.d.ts.map +1 -0
- package/dist/cli/output/components/progressBar.js +38 -0
- package/dist/cli/output/components/summary.d.ts +8 -0
- package/dist/cli/output/components/summary.d.ts.map +1 -0
- package/dist/cli/output/components/summary.js +12 -0
- package/dist/cli/output/components/warnings.d.ts +6 -0
- package/dist/cli/output/components/warnings.d.ts.map +1 -0
- package/dist/cli/output/components/warnings.js +11 -0
- package/dist/cli/output/formatter.d.ts +5 -0
- package/dist/cli/output/formatter.d.ts.map +1 -0
- package/dist/cli/output/formatter.js +28 -0
- package/dist/cli/output/index.d.ts +8 -0
- package/dist/cli/output/index.d.ts.map +1 -0
- package/dist/cli/output/index.js +4 -0
- package/dist/cli/output/renderer.d.ts +21 -0
- package/dist/cli/output/renderer.d.ts.map +1 -0
- package/dist/cli/output/renderer.js +121 -0
- package/dist/cli/output/styles.d.ts +23 -0
- package/dist/cli/output/styles.d.ts.map +1 -0
- package/dist/cli/output/styles.js +26 -0
- package/dist/config/defaults.d.ts +3 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +2 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +5 -0
- package/dist/config/loader.d.ts +3 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +11 -0
- package/dist/config/resolver.d.ts +8 -0
- package/dist/config/resolver.d.ts.map +1 -0
- package/dist/config/resolver.js +66 -0
- package/dist/config/types.d.ts +40 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +1 -0
- package/dist/config/utils.d.ts +7 -0
- package/dist/config/utils.d.ts.map +1 -0
- package/dist/config/utils.js +161 -0
- package/dist/core/ignoreMatcher.d.ts +15 -0
- package/dist/core/ignoreMatcher.d.ts.map +1 -0
- package/dist/core/ignoreMatcher.js +97 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +7 -0
- package/dist/core/renderOutput.d.ts +4 -0
- package/dist/core/renderOutput.d.ts.map +1 -0
- package/dist/core/renderOutput.js +218 -0
- package/dist/core/renderTree.d.ts +3 -0
- package/dist/core/renderTree.d.ts.map +1 -0
- package/dist/core/renderTree.js +23 -0
- package/dist/core/scanTree.d.ts +4 -0
- package/dist/core/scanTree.d.ts.map +1 -0
- package/dist/core/scanTree.js +348 -0
- package/dist/core/size.d.ts +4 -0
- package/dist/core/size.d.ts.map +1 -0
- package/dist/core/size.js +32 -0
- package/dist/core/statsCollector.d.ts +4 -0
- package/dist/core/statsCollector.d.ts.map +1 -0
- package/dist/core/statsCollector.js +28 -0
- package/dist/core/types.d.ts +53 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/infra/countTokens.d.ts +2 -0
- package/dist/infra/countTokens.d.ts.map +1 -0
- package/dist/infra/countTokens.js +186 -0
- package/dist/infra/datetime.d.ts +3 -0
- package/dist/infra/datetime.d.ts.map +1 -0
- package/dist/infra/datetime.js +15 -0
- package/dist/infra/index.d.ts +7 -0
- package/dist/infra/index.d.ts.map +1 -0
- package/dist/infra/index.js +6 -0
- package/dist/infra/logger.d.ts +19 -0
- package/dist/infra/logger.d.ts.map +1 -0
- package/dist/infra/logger.js +99 -0
- package/dist/infra/outputWriter.d.ts +15 -0
- package/dist/infra/outputWriter.d.ts.map +1 -0
- package/dist/infra/outputWriter.js +35 -0
- package/dist/infra/terminal.d.ts +91 -0
- package/dist/infra/terminal.d.ts.map +1 -0
- package/dist/infra/terminal.js +189 -0
- package/dist/infra/usageTracker.d.ts +3 -0
- package/dist/infra/usageTracker.d.ts.map +1 -0
- package/dist/infra/usageTracker.js +39 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +1 -0
- 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
|
+
[](https://github.com/nesvet/fln/actions/workflows/ci.yaml)
|
|
4
|
+
[](https://www.npmjs.com/package/fln)
|
|
5
|
+
[](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 @@
|
|
|
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"}
|
package/dist/api/fln.js
ADDED
|
@@ -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 @@
|
|
|
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,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 @@
|
|
|
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 @@
|
|
|
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"}
|
package/dist/cli/help.js
ADDED
|
@@ -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 @@
|
|
|
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";
|