oni-cli 1.0.0__py3-none-any.whl
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.
- oni/oni.py +384 -0
- oni_cli-1.0.0.dist-info/METADATA +126 -0
- oni_cli-1.0.0.dist-info/RECORD +6 -0
- oni_cli-1.0.0.dist-info/WHEEL +5 -0
- oni_cli-1.0.0.dist-info/entry_points.txt +2 -0
- oni_cli-1.0.0.dist-info/top_level.txt +1 -0
oni/oni.py
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ONI (鬼)
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Annotated, List, Optional, Set
|
|
12
|
+
|
|
13
|
+
import pyfiglet
|
|
14
|
+
import questionary
|
|
15
|
+
import typer
|
|
16
|
+
from questionary import Style as QStyle
|
|
17
|
+
from rich import box
|
|
18
|
+
from rich.align import Align
|
|
19
|
+
from rich.console import Console, Group
|
|
20
|
+
from rich.panel import Panel
|
|
21
|
+
from rich.progress import (
|
|
22
|
+
BarColumn,
|
|
23
|
+
Progress,
|
|
24
|
+
SpinnerColumn,
|
|
25
|
+
TaskProgressColumn,
|
|
26
|
+
TextColumn,
|
|
27
|
+
)
|
|
28
|
+
from rich.syntax import Syntax
|
|
29
|
+
from rich.table import Table
|
|
30
|
+
from rich.text import Text
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------
|
|
33
|
+
# Palette
|
|
34
|
+
# ---------------------------------------------------------
|
|
35
|
+
COLOR_CRIMSON = "#e60033"
|
|
36
|
+
COLOR_EMBER = "#ff6a00"
|
|
37
|
+
COLOR_GOLD = "#e0a96d"
|
|
38
|
+
COLOR_BONE = "#f4f1de"
|
|
39
|
+
COLOR_IRON = "#3d3a45"
|
|
40
|
+
COLOR_ASH = "#6c757d"
|
|
41
|
+
|
|
42
|
+
console = Console()
|
|
43
|
+
|
|
44
|
+
CLI_DESCRIPTION = "Oni is a fast CLI tool that packages your files into a single context window."
|
|
45
|
+
|
|
46
|
+
app = typer.Typer(
|
|
47
|
+
name="oni",
|
|
48
|
+
help=f"[bold {COLOR_CRIMSON}]ONI (鬼)[/] :: {CLI_DESCRIPTION}",
|
|
49
|
+
rich_markup_mode="rich",
|
|
50
|
+
no_args_is_help=False,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Standard ignore sets spared from the maw
|
|
54
|
+
DEFAULT_IGNORES = {
|
|
55
|
+
".git",
|
|
56
|
+
"node_modules",
|
|
57
|
+
"__pycache__",
|
|
58
|
+
".venv",
|
|
59
|
+
"venv",
|
|
60
|
+
".pytest_cache",
|
|
61
|
+
".mypy_cache",
|
|
62
|
+
"dist",
|
|
63
|
+
"build",
|
|
64
|
+
".DS_Store",
|
|
65
|
+
".idea",
|
|
66
|
+
".vscode",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Extension-to-syntax mapping for code fences
|
|
70
|
+
EXT_MAP = {
|
|
71
|
+
".py": "python",
|
|
72
|
+
".js": "javascript",
|
|
73
|
+
".ts": "typescript",
|
|
74
|
+
".tsx": "tsx",
|
|
75
|
+
".jsx": "jsx",
|
|
76
|
+
".json": "json",
|
|
77
|
+
".yaml": "yaml",
|
|
78
|
+
".yml": "yaml",
|
|
79
|
+
".toml": "toml",
|
|
80
|
+
".md": "markdown",
|
|
81
|
+
".rs": "rust",
|
|
82
|
+
".go": "go",
|
|
83
|
+
".html": "html",
|
|
84
|
+
".css": "css",
|
|
85
|
+
".sh": "bash",
|
|
86
|
+
".zsh": "bash",
|
|
87
|
+
".sql": "sql",
|
|
88
|
+
".c": "c",
|
|
89
|
+
".cpp": "cpp",
|
|
90
|
+
".h": "c",
|
|
91
|
+
".hpp": "cpp",
|
|
92
|
+
".swift": "swift",
|
|
93
|
+
".kt": "kotlin",
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Questionary style tuned to iron and crimson
|
|
97
|
+
ONI_PROMPT_STYLE = QStyle(
|
|
98
|
+
[
|
|
99
|
+
("qmark", f"fg:{COLOR_CRIMSON} bold"),
|
|
100
|
+
("question", f"fg:{COLOR_BONE} bold"),
|
|
101
|
+
("answer", f"fg:{COLOR_EMBER} bold"),
|
|
102
|
+
("pointer", f"fg:{COLOR_CRIMSON} bold"),
|
|
103
|
+
("text", f"fg:{COLOR_BONE}"),
|
|
104
|
+
]
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def render_banner() -> None:
|
|
109
|
+
ascii_art = pyfiglet.figlet_format("ONI", font="alligator2")
|
|
110
|
+
header_text = Text(ascii_art, style=f"bold {COLOR_CRIMSON}")
|
|
111
|
+
sub_title = Text(
|
|
112
|
+
"INSTANT CONTEXT WINDOWS FROM YOUR FILES",
|
|
113
|
+
style=f"bold {COLOR_EMBER}",
|
|
114
|
+
)
|
|
115
|
+
console.print(
|
|
116
|
+
Panel(
|
|
117
|
+
Align.center(Group(header_text, sub_title)),
|
|
118
|
+
border_style=COLOR_CRIMSON,
|
|
119
|
+
box=box.DOUBLE,
|
|
120
|
+
padding=(0, 2),
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def is_binary(path: Path) -> bool:
|
|
126
|
+
"""Detect binary files that would choke the prompt buffer."""
|
|
127
|
+
try:
|
|
128
|
+
with open(path, "rb") as f:
|
|
129
|
+
chunk = f.read(1024)
|
|
130
|
+
return b"\x00" in chunk
|
|
131
|
+
except Exception:
|
|
132
|
+
return True
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def copy_to_macos_clipboard(payload: str) -> None:
|
|
136
|
+
"""Feeds the amalgamated payload directly to the macOS pbcopy buffer."""
|
|
137
|
+
process = subprocess.Popen(["pbcopy"], stdin=subprocess.PIPE, close_fds=True)
|
|
138
|
+
process.communicate(payload.encode("utf-8"))
|
|
139
|
+
if process.returncode != 0:
|
|
140
|
+
raise RuntimeError("pbcopy buffer rejected the payload.")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ---------------------------------------------------------
|
|
144
|
+
# Core Execution
|
|
145
|
+
# ---------------------------------------------------------
|
|
146
|
+
@app.callback(invoke_without_command=True)
|
|
147
|
+
def devour(
|
|
148
|
+
ctx: typer.Context,
|
|
149
|
+
patterns: Annotated[
|
|
150
|
+
Optional[List[str]],
|
|
151
|
+
typer.Argument(
|
|
152
|
+
help="File paths or glob patterns (e.g. 'src/**/*.py', 'main.py', '*.json'). Prompts if omitted."
|
|
153
|
+
),
|
|
154
|
+
] = None,
|
|
155
|
+
output_meta: Annotated[
|
|
156
|
+
bool,
|
|
157
|
+
typer.Option(
|
|
158
|
+
"--meta/--raw",
|
|
159
|
+
help="Include repository overview and file breakdown in LLM context header.",
|
|
160
|
+
),
|
|
161
|
+
] = True,
|
|
162
|
+
max_file_size_kb: Annotated[
|
|
163
|
+
int,
|
|
164
|
+
typer.Option(
|
|
165
|
+
"--max-kb", "-m", help="Skip files larger than this threshold in KB."
|
|
166
|
+
),
|
|
167
|
+
] = 512,
|
|
168
|
+
preview: Annotated[
|
|
169
|
+
bool,
|
|
170
|
+
typer.Option(
|
|
171
|
+
"--preview", "-p", help="Echo sample of amalgamated context to terminal."
|
|
172
|
+
),
|
|
173
|
+
] = False,
|
|
174
|
+
):
|
|
175
|
+
"""
|
|
176
|
+
Read codebase files and amalgamate them directly into your macOS clipboard.
|
|
177
|
+
"""
|
|
178
|
+
render_banner()
|
|
179
|
+
root_dir = Path.cwd()
|
|
180
|
+
|
|
181
|
+
# Interactive prompt if no patterns provided
|
|
182
|
+
if not patterns:
|
|
183
|
+
console.print(
|
|
184
|
+
f"[bold {COLOR_EMBER}]Specify file patterns to search for...[/]\n"
|
|
185
|
+
)
|
|
186
|
+
user_input = questionary.text(
|
|
187
|
+
"Enter target paths or globs to fetch (space-separated):",
|
|
188
|
+
default="**/*",
|
|
189
|
+
style=ONI_PROMPT_STYLE,
|
|
190
|
+
).ask()
|
|
191
|
+
|
|
192
|
+
if not user_input or not user_input.strip():
|
|
193
|
+
console.print(f"[{COLOR_ASH}]Aborted.[/]")
|
|
194
|
+
raise typer.Exit()
|
|
195
|
+
patterns = user_input.strip().split()
|
|
196
|
+
|
|
197
|
+
console.print(
|
|
198
|
+
f"[bold {COLOR_ASH}]Root Directory:[/] [bold {COLOR_BONE}]{root_dir}[/]"
|
|
199
|
+
)
|
|
200
|
+
console.print(
|
|
201
|
+
f"[bold {COLOR_ASH}]Glob Patterns:[/] [bold {COLOR_GOLD}]{', '.join(patterns)}[/]\n"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Step 1: Scan & resolve files
|
|
205
|
+
matching_files: Set[Path] = set()
|
|
206
|
+
|
|
207
|
+
with console.status(
|
|
208
|
+
f"[bold {COLOR_CRIMSON}]Fetching matching files across directories...",
|
|
209
|
+
spinner="aesthetic",
|
|
210
|
+
):
|
|
211
|
+
for pattern in patterns:
|
|
212
|
+
target = root_dir / pattern
|
|
213
|
+
if target.is_file():
|
|
214
|
+
matching_files.add(target.resolve())
|
|
215
|
+
elif target.is_dir():
|
|
216
|
+
for p in target.rglob("*"):
|
|
217
|
+
if p.is_file():
|
|
218
|
+
matching_files.add(p.resolve())
|
|
219
|
+
else:
|
|
220
|
+
for p in root_dir.glob(pattern):
|
|
221
|
+
if p.is_file():
|
|
222
|
+
matching_files.add(p.resolve())
|
|
223
|
+
|
|
224
|
+
# Filter out ignored directories
|
|
225
|
+
filtered_files: List[Path] = []
|
|
226
|
+
for f in matching_files:
|
|
227
|
+
rel = f.relative_to(root_dir)
|
|
228
|
+
if any(ignored in rel.parts for ignored in DEFAULT_IGNORES):
|
|
229
|
+
continue
|
|
230
|
+
filtered_files.append(f)
|
|
231
|
+
|
|
232
|
+
filtered_files.sort()
|
|
233
|
+
|
|
234
|
+
if not filtered_files:
|
|
235
|
+
console.print(
|
|
236
|
+
Panel(
|
|
237
|
+
f"[bold {COLOR_CRIMSON}]Nothing found.[/]\n"
|
|
238
|
+
f"[{COLOR_ASH}]No files matched the specified patterns or all were filtered by ignore rules (.git, node_modules, etc).[/]",
|
|
239
|
+
border_style=COLOR_CRIMSON,
|
|
240
|
+
box=box.ROUNDED,
|
|
241
|
+
)
|
|
242
|
+
)
|
|
243
|
+
raise typer.Exit()
|
|
244
|
+
|
|
245
|
+
# Step 2: Ingest, format, and display progress
|
|
246
|
+
payload_parts: List[str] = []
|
|
247
|
+
devour_table = Table(
|
|
248
|
+
box=box.HORIZONTALS,
|
|
249
|
+
border_style=COLOR_IRON,
|
|
250
|
+
title=f"[bold {COLOR_CRIMSON}]LOG[/]",
|
|
251
|
+
expand=True,
|
|
252
|
+
)
|
|
253
|
+
devour_table.add_column("#", style=f"bold {COLOR_ASH}", width=5)
|
|
254
|
+
devour_table.add_column("File", style=f"bold {COLOR_BONE}")
|
|
255
|
+
devour_table.add_column("Lines", style=COLOR_GOLD, justify="right")
|
|
256
|
+
devour_table.add_column("Weight", style=COLOR_EMBER, justify="right")
|
|
257
|
+
devour_table.add_column("Fate", style=COLOR_CRIMSON, justify="center")
|
|
258
|
+
|
|
259
|
+
if output_meta:
|
|
260
|
+
payload_parts.append(
|
|
261
|
+
f"# FILE CONTEXT\n"
|
|
262
|
+
f"- Project Root: `{root_dir.name}`\n"
|
|
263
|
+
f"- Total Files: {len(filtered_files)}\n\n"
|
|
264
|
+
f"The following files have been added into the context window "
|
|
265
|
+
f"for full comprehension and context.\n\n"
|
|
266
|
+
f"---\n\n"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
total_bytes = 0
|
|
270
|
+
total_lines = 0
|
|
271
|
+
|
|
272
|
+
with Progress(
|
|
273
|
+
SpinnerColumn("bouncingBar", style=f"bold {COLOR_CRIMSON}"),
|
|
274
|
+
TextColumn(f"[bold {COLOR_BONE}]{{task.description}}"),
|
|
275
|
+
BarColumn(
|
|
276
|
+
bar_width=35,
|
|
277
|
+
style=COLOR_IRON,
|
|
278
|
+
complete_style=COLOR_CRIMSON,
|
|
279
|
+
finished_style=COLOR_EMBER,
|
|
280
|
+
),
|
|
281
|
+
TaskProgressColumn(style=f"bold {COLOR_GOLD}"),
|
|
282
|
+
console=console,
|
|
283
|
+
) as progress:
|
|
284
|
+
task = progress.add_task("Reading files...", total=len(filtered_files))
|
|
285
|
+
|
|
286
|
+
for idx, file_path in enumerate(filtered_files, start=1):
|
|
287
|
+
rel_path = file_path.relative_to(root_dir)
|
|
288
|
+
size_kb = file_path.stat().st_size / 1024
|
|
289
|
+
|
|
290
|
+
if is_binary(file_path):
|
|
291
|
+
devour_table.add_row(
|
|
292
|
+
f"{idx:02d}",
|
|
293
|
+
str(rel_path),
|
|
294
|
+
"—",
|
|
295
|
+
f"{size_kb:.1f} KB",
|
|
296
|
+
f"[{COLOR_ASH}](BINARY)[/]",
|
|
297
|
+
)
|
|
298
|
+
progress.update(task, advance=1)
|
|
299
|
+
continue
|
|
300
|
+
|
|
301
|
+
if size_kb > max_file_size_kb:
|
|
302
|
+
devour_table.add_row(
|
|
303
|
+
f"{idx:02d}",
|
|
304
|
+
str(rel_path),
|
|
305
|
+
"—",
|
|
306
|
+
f"{size_kb:.1f} KB",
|
|
307
|
+
f"[{COLOR_EMBER}]SPAT OUT (> {max_file_size_kb}KB)[/]",
|
|
308
|
+
)
|
|
309
|
+
progress.update(task, advance=1)
|
|
310
|
+
continue
|
|
311
|
+
|
|
312
|
+
try:
|
|
313
|
+
content = file_path.read_text(encoding="utf-8", errors="replace")
|
|
314
|
+
lines_count = len(content.splitlines())
|
|
315
|
+
total_lines += lines_count
|
|
316
|
+
total_bytes += file_path.stat().st_size
|
|
317
|
+
lang = EXT_MAP.get(file_path.suffix.lower(), "")
|
|
318
|
+
|
|
319
|
+
# LLM code fence formatting
|
|
320
|
+
shard_block = f"## File: `{rel_path}`\n```{lang}\n{content}\n```\n\n"
|
|
321
|
+
payload_parts.append(shard_block)
|
|
322
|
+
|
|
323
|
+
devour_table.add_row(
|
|
324
|
+
f"{idx:02d}",
|
|
325
|
+
str(rel_path),
|
|
326
|
+
f"{lines_count:,}",
|
|
327
|
+
f"{size_kb:.1f} KB",
|
|
328
|
+
f"[bold {COLOR_CRIMSON}]DEVOUR[/]",
|
|
329
|
+
)
|
|
330
|
+
except Exception as e:
|
|
331
|
+
devour_table.add_row(
|
|
332
|
+
f"{idx:02d}", str(rel_path), "ERR", "0 KB", f"[red]{str(e)[:15]}[/]"
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
progress.update(task, advance=1)
|
|
336
|
+
|
|
337
|
+
console.print()
|
|
338
|
+
console.print(devour_table)
|
|
339
|
+
|
|
340
|
+
final_payload = "".join(payload_parts)
|
|
341
|
+
|
|
342
|
+
if not final_payload.strip():
|
|
343
|
+
console.print(f"[{COLOR_CRIMSON}]No source content was found.[/]")
|
|
344
|
+
raise typer.Exit()
|
|
345
|
+
|
|
346
|
+
# Step 3: Direct Ingestion into macOS Clipboard
|
|
347
|
+
with console.status(
|
|
348
|
+
f"[bold {COLOR_CRIMSON}]Binding context into macOS clipboard...", spinner="line"
|
|
349
|
+
):
|
|
350
|
+
try:
|
|
351
|
+
copy_to_macos_clipboard(final_payload)
|
|
352
|
+
except Exception as err:
|
|
353
|
+
console.print(f"[bold red]Failed to deliver payload to clipboard:[/] {err}")
|
|
354
|
+
raise typer.Exit(code=1)
|
|
355
|
+
|
|
356
|
+
# Culmination / Victory Panel
|
|
357
|
+
stats_msg = (
|
|
358
|
+
f"[bold {COLOR_CRIMSON}]COMPLETE // PROMPT CREATED[/]\n\n"
|
|
359
|
+
f"• [bold {COLOR_BONE}]Clipboard State:[/] [bold {COLOR_EMBER}]Copied[/]\n"
|
|
360
|
+
f"• [bold {COLOR_BONE}]Files Combined:[/] [bold {COLOR_GOLD}]{len(filtered_files)}[/]\n"
|
|
361
|
+
f"• [bold {COLOR_BONE}]Total Lines:[/] [bold {COLOR_GOLD}]{total_lines:,}[/]\n"
|
|
362
|
+
f"• [bold {COLOR_BONE}]Context Size:[/] [bold {COLOR_GOLD}]{total_bytes / 1024:.2f} KB[/]\n\n"
|
|
363
|
+
f"[{COLOR_ASH}]Press [bold {COLOR_BONE}]⌘ + V[/] to paste the content.[/]"
|
|
364
|
+
)
|
|
365
|
+
console.print()
|
|
366
|
+
console.print(Panel(stats_msg, border_style=COLOR_CRIMSON, box=box.HEAVY))
|
|
367
|
+
|
|
368
|
+
# Step 4: Optional terminal preview
|
|
369
|
+
if preview:
|
|
370
|
+
console.print(f"\n[bold {COLOR_EMBER}]// PROMPT PREVIEW //[/]\n")
|
|
371
|
+
syntax = Syntax(
|
|
372
|
+
final_payload[:2500], "markdown", theme="ansi_dark", line_numbers=True
|
|
373
|
+
)
|
|
374
|
+
console.print(
|
|
375
|
+
Panel(
|
|
376
|
+
syntax,
|
|
377
|
+
title=f"[{COLOR_ASH}]Excerpt (First 2500 chars)[/]",
|
|
378
|
+
border_style=COLOR_IRON,
|
|
379
|
+
)
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
if __name__ == "__main__":
|
|
384
|
+
app()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: oni-cli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Single-command context aggregation for LLMs, straight to your macOS clipboard.
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: pyfiglet>=1.0.4
|
|
8
|
+
Requires-Dist: questionary>=2.1.1
|
|
9
|
+
Requires-Dist: rich>=15.0.0
|
|
10
|
+
Requires-Dist: typer>=0.27.2
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
<div align="center">
|
|
14
|
+
|
|
15
|
+
<img src="https://rowlinsonmike.com/oni.png" alt="ONI Logo" width="180" />
|
|
16
|
+
|
|
17
|
+
# ONI
|
|
18
|
+
|
|
19
|
+
**Single-command context aggregation for LLMs, straight to your macOS clipboard.**
|
|
20
|
+
|
|
21
|
+
<br />
|
|
22
|
+
|
|
23
|
+
<img src="https://rowlinsonmike.com/oni-demo.gif" alt="ONI Demo" width="750" />
|
|
24
|
+
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
CLI that makes a single context widow from your files and sends it directly to your macOS clipboard.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Key Features
|
|
34
|
+
|
|
35
|
+
- **Direct to Clipboard**: Uses macOS `pbcopy` under the hood to send your aggregated code context straight to your clipboard.
|
|
36
|
+
- **Smart Filtering**: Ignores binary files and junk directories (`.git`, `node_modules`, `.venv`, build caches) automatically.
|
|
37
|
+
- **Syntax Awareness**: Maps file extensions to Markdown code fences (`py`, `ts`, `rs`, `go`, `sql`, etc.) for LLM legibility.
|
|
38
|
+
- **Interactive Prompting**: If run with no arguments, Oni prompts for target glob patterns.
|
|
39
|
+
- **Size Safeguards**: Skips oversized source files with customizable thresholds.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
Ensure you have Python 3.9+ installed:
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Quick Usage
|
|
51
|
+
|
|
52
|
+
### Parse everything in the current directory
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
oni
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
*If called without patterns, Oni triggers an interactive prompt defaulting to `**/*`.*
|
|
60
|
+
|
|
61
|
+
### Parse specific directories or file globs
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Ingest entire src directory
|
|
65
|
+
oni src/**/*
|
|
66
|
+
|
|
67
|
+
# Combine specific file types
|
|
68
|
+
oni "src/**/*.py" "config/*.json"
|
|
69
|
+
|
|
70
|
+
# Ingest explicit targets
|
|
71
|
+
oni main.py pyproject.toml docs/README.md
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## CLI Options Reference
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
Usage: oni [OPTIONS] [PATTERNS]...
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
| Flag / Option | Short | Default | Description |
|
|
85
|
+
| --- | --- | --- | --- |
|
|
86
|
+
| `[PATTERNS]...` | — | `None` (Prompts) | File paths or glob patterns (e.g. `src/**/*.py`, `main.py`). |
|
|
87
|
+
| `--meta / --raw` | — | `--meta` | Includes repository file context header and aggregate statistics. |
|
|
88
|
+
| `--max-kb` | `-m` | `512` | Upper size limit in KB per file. Larger files are spat out. |
|
|
89
|
+
| `--preview` | `-p` | `False` | Prints the first 2,500 characters of the prompt to terminal. |
|
|
90
|
+
| `--help` | — | — | Displays the command-line usage and exits. |
|
|
91
|
+
|
|
92
|
+
### Examples with Flags
|
|
93
|
+
|
|
94
|
+
#### Omit Metadata Header
|
|
95
|
+
|
|
96
|
+
Generate pure code fence blocks with no introductory repository summaries:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
oni "src/**/*.rs" --raw
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### Adjust Max File Size Threshold
|
|
104
|
+
|
|
105
|
+
Lower file size limit to 200 KB and inspect the payload before pasting:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
oni "**/*.ts" --max-kb 200 --preview
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Automatic Exclusions
|
|
115
|
+
|
|
116
|
+
Oni ignores the following paths during scanning:
|
|
117
|
+
|
|
118
|
+
```text
|
|
119
|
+
.git node_modules __pycache__
|
|
120
|
+
.venv venv .pytest_cache
|
|
121
|
+
.mypy_cache dist build
|
|
122
|
+
.DS_Store .idea .vscode
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Binary files are detected via null-byte inspection and excluded from the output buffer.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
oni/oni.py,sha256=SsGf_olqYCMWrillRqZlXCXN5Okfo0Wy7gNUo8QABbs,11854
|
|
2
|
+
oni_cli-1.0.0.dist-info/METADATA,sha256=T2JQVcVbi4IaOwbyFiAxaaI6LXWCWtj7yiESw_UbcLI,3085
|
|
3
|
+
oni_cli-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
4
|
+
oni_cli-1.0.0.dist-info/entry_points.txt,sha256=yE9SVtyFFlSYMijdNBm5DUtD_vBYg3QWuOgTzQ6mtZo,36
|
|
5
|
+
oni_cli-1.0.0.dist-info/top_level.txt,sha256=y46kSeGeBZ2tP2IRm8OwnYdiwUJbsD4EcvLLbt7D5JU,4
|
|
6
|
+
oni_cli-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
oni
|