ctxcode 0.2.0__tar.gz

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.
@@ -0,0 +1,7 @@
1
+ *.egg-info/
2
+ dist/
3
+ build/
4
+ __pycache__/
5
+ *.pyc
6
+ .venv/
7
+ venv/
ctxcode-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mohammed Hanan
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.
ctxcode-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,202 @@
1
+ Metadata-Version: 2.4
2
+ Name: ctxcode
3
+ Version: 0.2.0
4
+ Summary: Extract your codebase context for AI assistants
5
+ Project-URL: Homepage, https://github.com/hanu-14/codecontext
6
+ Project-URL: Repository, https://github.com/hanu-14/codecontext
7
+ Project-URL: Issues, https://github.com/hanu-14/codecontext/issues
8
+ Author-email: Mohammed Hanan <hananmtp313@gmail.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai,cli,code-review,developer-tools,productivity
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Software Development :: Code Generators
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+
21
+ # codecontext
22
+
23
+ <p align="center">
24
+ <b>Extract your codebase context for AI assistants.</b><br>
25
+ <a href="https://github.com/hanu-14/codecontext/actions/workflows/ci.yml">
26
+ <img src="https://github.com/hanu-14/codecontext/actions/workflows/ci.yml/badge.svg" alt="CI">
27
+ </a>
28
+ <img src="https://img.shields.io/badge/python-3.9%2B-blue" alt="Python 3.9+">
29
+ <img src="https://img.shields.io/badge/license-MIT-green" alt="MIT">
30
+ <img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero dependencies">
31
+ <img src="https://img.shields.io/badge/version-0.2.0-blue" alt="v0.2.0">
32
+ </p>
33
+
34
+ A zero-dependency CLI tool that packages your project structure, key files, and git history into a formatted prompt for Claude, ChatGPT, and other AI coding assistants.
35
+
36
+ ```bash
37
+ pip install ctxcode
38
+ cd my-project
39
+ codecontext
40
+ ```
41
+
42
+ ## Why?
43
+
44
+ When asking AI assistants about your code, you spend minutes copy-pasting file trees, key files, and git history. `codecontext` does it in one command — giving the AI full project context for better answers.
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pip install codecontext
50
+ ```
51
+
52
+ Or run directly:
53
+
54
+ ```bash
55
+ curl -O https://raw.githubusercontent.com/hanu-14/codecontext/main/src/codecontext/cli.py
56
+ python cli.py
57
+ ```
58
+
59
+ ## Usage
60
+
61
+ ```bash
62
+ # Full context (tree + files + git history)
63
+ codecontext
64
+
65
+ # Specific project
66
+ codecontext path/to/project
67
+
68
+ # Output formats
69
+ codecontext --format markdown # default - ready for AI chat
70
+ codecontext --format plain # plain text
71
+ codecontext --format json # machine-readable
72
+
73
+ # Copy to clipboard
74
+ codecontext --clipboard
75
+
76
+ # Save to file
77
+ codecontext -o context.md
78
+
79
+ # Exclude custom patterns
80
+ codecontext --exclude "*.log" "tmp/" "*.csv"
81
+
82
+ # Show hidden files
83
+ codecontext --hidden
84
+
85
+ # Exclude git info
86
+ codecontext --no-git
87
+
88
+ # Adjust limits
89
+ codecontext --max-files 100 --max-lines 150
90
+ ```
91
+
92
+ ### Short alias
93
+
94
+ ```bash
95
+ ctx # same as codecontext
96
+ ctx ./src # specific directory
97
+ ctx --clipboard # copy to clipboard
98
+ ```
99
+
100
+ ## Configuration
101
+
102
+ Place a `.codecontext.toml` in your project root:
103
+
104
+ ```toml
105
+ git = true
106
+ max_files = 300
107
+ max_lines = 250
108
+ format = "markdown"
109
+ exclude = ["*.log", "tmp/"]
110
+ hidden = false
111
+ ```
112
+
113
+ JSON and YAML formats are also supported (`.codecontext.json`, `.codecontext.yml`).
114
+
115
+ ## Output
116
+
117
+ The tool produces a structured document with:
118
+
119
+ 1. **Project metadata** — path, git remote, branch
120
+ 2. **Git history** — recent commits and uncommitted changes
121
+ 3. **File tree** — with file sizes for every entry
122
+ 4. **Key files** — source files with syntax highlighting
123
+ 5. **Stats summary** — total files and size at a glance
124
+
125
+ Perfect for pasting directly into Claude, ChatGPT, or any AI coding assistant.
126
+
127
+ ## Features
128
+
129
+ - **Zero dependencies** — pure Python, no pip installs beyond this package
130
+ - **Smart ignore** — automatically skips `.git`, `node_modules`, `__pycache__`, binaries, and more
131
+ - **Git aware** — includes branch, remote, recent commits, and diff stats
132
+ - **File limit safety** — won't overwhelm your context window
133
+ - **Language detection** — syntax-highlighted code blocks for 30+ languages
134
+ - **Clipboard support** — `--clipboard` copies output directly to your clipboard
135
+ - **Multiple formats** — markdown, plain text, or JSON
136
+ - **Config file** — project-specific settings via `.codecontext.toml`
137
+ - **Custom excludes** — ignore additional patterns with `--exclude`
138
+ - **File sizes** — every file in the tree shows its size
139
+
140
+ ## Example
141
+
142
+ ```bash
143
+ $ codecontext --format markdown
144
+ ```
145
+
146
+ Produces:
147
+
148
+ ```
149
+ # Codebase Context
150
+
151
+ Generated from: /Users/me/my-project
152
+ Remote: git@github.com:user/my-project.git
153
+ Branch: main
154
+
155
+ Recent commits:
156
+ abc1234 feat: add user authentication
157
+ def5678 fix: resolve payment timeout
158
+
159
+ ## Project Structure
160
+
161
+ my-project/
162
+ ├── src/
163
+ │ ├── main.py (2KB)
164
+ │ └── utils.py (1KB)
165
+ ├── tests/
166
+ │ └── test_main.py (3KB)
167
+ ├── pyproject.toml (1KB)
168
+ └── README.md (3KB)
169
+
170
+ ## Key Files (6 files, 10KB)
171
+
172
+ ### `src/main.py`
173
+
174
+ ```python
175
+ def greet(name):
176
+ return f"Hello, {name}!"
177
+ ```
178
+
179
+ ...
180
+ ```
181
+
182
+ ## Changelog
183
+
184
+ ### v0.2.0
185
+ - Clipboard support (`--clipboard`)
186
+ - Multiple output formats: markdown, plain, JSON
187
+ - Configuration file support (`.codecontext.toml`, `.json`, `.yml`)
188
+ - File sizes in tree output
189
+ - Custom exclude patterns (`--exclude`)
190
+ - Hidden files flag (`--hidden`)
191
+ - Stats summary in output
192
+
193
+ ### v0.1.0
194
+ - Initial release
195
+ - File tree with smart ignore patterns
196
+ - Git history integration
197
+ - Syntax highlighting for 30+ languages
198
+ - CLI alias `ctx`
199
+
200
+ ## License
201
+
202
+ MIT
@@ -0,0 +1,182 @@
1
+ # codecontext
2
+
3
+ <p align="center">
4
+ <b>Extract your codebase context for AI assistants.</b><br>
5
+ <a href="https://github.com/hanu-14/codecontext/actions/workflows/ci.yml">
6
+ <img src="https://github.com/hanu-14/codecontext/actions/workflows/ci.yml/badge.svg" alt="CI">
7
+ </a>
8
+ <img src="https://img.shields.io/badge/python-3.9%2B-blue" alt="Python 3.9+">
9
+ <img src="https://img.shields.io/badge/license-MIT-green" alt="MIT">
10
+ <img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero dependencies">
11
+ <img src="https://img.shields.io/badge/version-0.2.0-blue" alt="v0.2.0">
12
+ </p>
13
+
14
+ A zero-dependency CLI tool that packages your project structure, key files, and git history into a formatted prompt for Claude, ChatGPT, and other AI coding assistants.
15
+
16
+ ```bash
17
+ pip install ctxcode
18
+ cd my-project
19
+ codecontext
20
+ ```
21
+
22
+ ## Why?
23
+
24
+ When asking AI assistants about your code, you spend minutes copy-pasting file trees, key files, and git history. `codecontext` does it in one command — giving the AI full project context for better answers.
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ pip install codecontext
30
+ ```
31
+
32
+ Or run directly:
33
+
34
+ ```bash
35
+ curl -O https://raw.githubusercontent.com/hanu-14/codecontext/main/src/codecontext/cli.py
36
+ python cli.py
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ```bash
42
+ # Full context (tree + files + git history)
43
+ codecontext
44
+
45
+ # Specific project
46
+ codecontext path/to/project
47
+
48
+ # Output formats
49
+ codecontext --format markdown # default - ready for AI chat
50
+ codecontext --format plain # plain text
51
+ codecontext --format json # machine-readable
52
+
53
+ # Copy to clipboard
54
+ codecontext --clipboard
55
+
56
+ # Save to file
57
+ codecontext -o context.md
58
+
59
+ # Exclude custom patterns
60
+ codecontext --exclude "*.log" "tmp/" "*.csv"
61
+
62
+ # Show hidden files
63
+ codecontext --hidden
64
+
65
+ # Exclude git info
66
+ codecontext --no-git
67
+
68
+ # Adjust limits
69
+ codecontext --max-files 100 --max-lines 150
70
+ ```
71
+
72
+ ### Short alias
73
+
74
+ ```bash
75
+ ctx # same as codecontext
76
+ ctx ./src # specific directory
77
+ ctx --clipboard # copy to clipboard
78
+ ```
79
+
80
+ ## Configuration
81
+
82
+ Place a `.codecontext.toml` in your project root:
83
+
84
+ ```toml
85
+ git = true
86
+ max_files = 300
87
+ max_lines = 250
88
+ format = "markdown"
89
+ exclude = ["*.log", "tmp/"]
90
+ hidden = false
91
+ ```
92
+
93
+ JSON and YAML formats are also supported (`.codecontext.json`, `.codecontext.yml`).
94
+
95
+ ## Output
96
+
97
+ The tool produces a structured document with:
98
+
99
+ 1. **Project metadata** — path, git remote, branch
100
+ 2. **Git history** — recent commits and uncommitted changes
101
+ 3. **File tree** — with file sizes for every entry
102
+ 4. **Key files** — source files with syntax highlighting
103
+ 5. **Stats summary** — total files and size at a glance
104
+
105
+ Perfect for pasting directly into Claude, ChatGPT, or any AI coding assistant.
106
+
107
+ ## Features
108
+
109
+ - **Zero dependencies** — pure Python, no pip installs beyond this package
110
+ - **Smart ignore** — automatically skips `.git`, `node_modules`, `__pycache__`, binaries, and more
111
+ - **Git aware** — includes branch, remote, recent commits, and diff stats
112
+ - **File limit safety** — won't overwhelm your context window
113
+ - **Language detection** — syntax-highlighted code blocks for 30+ languages
114
+ - **Clipboard support** — `--clipboard` copies output directly to your clipboard
115
+ - **Multiple formats** — markdown, plain text, or JSON
116
+ - **Config file** — project-specific settings via `.codecontext.toml`
117
+ - **Custom excludes** — ignore additional patterns with `--exclude`
118
+ - **File sizes** — every file in the tree shows its size
119
+
120
+ ## Example
121
+
122
+ ```bash
123
+ $ codecontext --format markdown
124
+ ```
125
+
126
+ Produces:
127
+
128
+ ```
129
+ # Codebase Context
130
+
131
+ Generated from: /Users/me/my-project
132
+ Remote: git@github.com:user/my-project.git
133
+ Branch: main
134
+
135
+ Recent commits:
136
+ abc1234 feat: add user authentication
137
+ def5678 fix: resolve payment timeout
138
+
139
+ ## Project Structure
140
+
141
+ my-project/
142
+ ├── src/
143
+ │ ├── main.py (2KB)
144
+ │ └── utils.py (1KB)
145
+ ├── tests/
146
+ │ └── test_main.py (3KB)
147
+ ├── pyproject.toml (1KB)
148
+ └── README.md (3KB)
149
+
150
+ ## Key Files (6 files, 10KB)
151
+
152
+ ### `src/main.py`
153
+
154
+ ```python
155
+ def greet(name):
156
+ return f"Hello, {name}!"
157
+ ```
158
+
159
+ ...
160
+ ```
161
+
162
+ ## Changelog
163
+
164
+ ### v0.2.0
165
+ - Clipboard support (`--clipboard`)
166
+ - Multiple output formats: markdown, plain, JSON
167
+ - Configuration file support (`.codecontext.toml`, `.json`, `.yml`)
168
+ - File sizes in tree output
169
+ - Custom exclude patterns (`--exclude`)
170
+ - Hidden files flag (`--hidden`)
171
+ - Stats summary in output
172
+
173
+ ### v0.1.0
174
+ - Initial release
175
+ - File tree with smart ignore patterns
176
+ - Git history integration
177
+ - Syntax highlighting for 30+ languages
178
+ - CLI alias `ctx`
179
+
180
+ ## License
181
+
182
+ MIT
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ctxcode"
7
+ version = "0.2.0"
8
+ description = "Extract your codebase context for AI assistants"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ {name = "Mohammed Hanan", email = "hananmtp313@gmail.com"},
14
+ ]
15
+ keywords = ["cli", "ai", "code-review", "productivity", "developer-tools"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "Topic :: Software Development :: Code Generators",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Programming Language :: Python :: 3",
23
+ ]
24
+ dependencies = []
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/hanu-14/codecontext"
28
+ Repository = "https://github.com/hanu-14/codecontext"
29
+ Issues = "https://github.com/hanu-14/codecontext/issues"
30
+
31
+ [project.scripts]
32
+ codecontext = "codecontext.cli:main"
33
+ ctx = "codecontext.cli:main"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["src/codecontext"]
37
+
38
+ [tool.hatch.build.targets.sdist]
39
+ include = ["/src"]
File without changes
@@ -0,0 +1,249 @@
1
+ import json
2
+ import os
3
+ import shutil
4
+ import sys
5
+ import textwrap
6
+
7
+ from . import scanner
8
+ from . import git_utils
9
+
10
+
11
+ LANG_MAP = {
12
+ ".py": "python", ".js": "javascript", ".ts": "typescript",
13
+ ".tsx": "tsx", ".jsx": "jsx", ".rs": "rust", ".go": "go",
14
+ ".java": "java", ".c": "c", ".cpp": "cpp", ".h": "c",
15
+ ".hpp": "cpp", ".rb": "ruby", ".php": "php", ".swift": "swift",
16
+ ".kt": "kotlin", ".scala": "scala", ".r": "r",
17
+ ".yaml": "yaml", ".yml": "yaml", ".json": "json",
18
+ ".toml": "toml", ".xml": "xml", ".md": "markdown",
19
+ ".html": "html", ".css": "css", ".scss": "scss",
20
+ ".sql": "sql", ".sh": "bash", ".bash": "bash",
21
+ ".zsh": "bash", ".dockerfile": "dockerfile",
22
+ ".txt": "text", ".cfg": "ini", ".ini": "ini",
23
+ }
24
+
25
+
26
+ def build_stats(root, key_files):
27
+ total_files = len(key_files)
28
+ total_size = 0
29
+ for _, abs_path in key_files:
30
+ try:
31
+ total_size += os.path.getsize(abs_path)
32
+ except OSError:
33
+ pass
34
+ return {"files": total_files, "size": total_size, "dir": root}
35
+
36
+
37
+ def format_markdown(root, include_git, max_files, max_lines, extra_ignore, show_hidden):
38
+ blocks = []
39
+
40
+ blocks.append("# Codebase Context\n")
41
+ blocks.append(f"Generated from: `{os.path.abspath(root)}`\n")
42
+
43
+ if include_git:
44
+ info = git_utils.get_git_info(root)
45
+ if info.get("remote"):
46
+ blocks.append(f"Remote: `{info['remote']}`")
47
+ if info.get("branch"):
48
+ blocks.append(f"Branch: `{info['branch']}`")
49
+ if info.get("uncommitted"):
50
+ blocks.append(f"\nUncommitted changes:\n```\n{info['uncommitted']}\n```")
51
+ if info.get("recent_commits"):
52
+ blocks.append(f"\nRecent commits:\n```\n{info['recent_commits']}\n```")
53
+
54
+ tree = scanner.get_tree(root, max_files=max_files, extra_ignore=extra_ignore, show_hidden=show_hidden)
55
+ blocks.append(f"\n## Project Structure\n\n```\n{tree}\n```\n")
56
+
57
+ key_files = scanner.find_key_files(root, extra_ignore=extra_ignore)
58
+ stats = build_stats(root, key_files)
59
+
60
+ blocks.append(f"\n## Key Files ({stats['files']} files, {stats['size'] / 1024:.0f}KB)\n")
61
+
62
+ for rel_path, abs_path in key_files:
63
+ ext = os.path.splitext(rel_path)[1].lower()
64
+ lang = LANG_MAP.get(ext, "")
65
+ content = scanner.read_file(abs_path, max_lines=max_lines)
66
+ blocks.append(f"\n### `{rel_path}`\n\n```{lang}\n{content}```")
67
+
68
+ blocks.append("\n---\n*Generated by [codecontext](https://github.com/hanu-14/codecontext)*")
69
+ return "\n".join(blocks)
70
+
71
+
72
+ def format_plain(root, include_git, max_files, max_lines, extra_ignore, show_hidden):
73
+ lines = []
74
+
75
+ lines.append(f"Project: {os.path.abspath(root)}\n")
76
+
77
+ if include_git:
78
+ info = git_utils.get_git_info(root)
79
+ if info.get("remote"):
80
+ lines.append(f"Remote: {info['remote']}")
81
+ if info.get("branch"):
82
+ lines.append(f"Branch: {info['branch']}")
83
+ if info.get("uncommitted"):
84
+ lines.append(f"\nUncommitted:\n{info['uncommitted']}")
85
+
86
+ lines.append(f"\nProject Structure:\n{scanner.get_tree(root, max_files=max_files, extra_ignore=extra_ignore, show_hidden=show_hidden)}\n")
87
+
88
+ key_files = scanner.find_key_files(root, extra_ignore=extra_ignore)
89
+ for rel_path, abs_path in key_files:
90
+ content = scanner.read_file(abs_path, max_lines=max_lines)
91
+ sep = "=" * 60
92
+ lines.append(f"\n{sep}\nFile: {rel_path}\n{sep}\n{content}")
93
+
94
+ return "\n".join(lines)
95
+
96
+
97
+ def format_json(root, include_git, max_files, max_lines, extra_ignore, show_hidden):
98
+ info = {}
99
+ info["root"] = os.path.abspath(root)
100
+
101
+ if include_git:
102
+ info["git"] = git_utils.get_git_info(root)
103
+
104
+ info["tree"] = scanner.get_tree(root, max_files=max_files, extra_ignore=extra_ignore, show_hidden=show_hidden)
105
+
106
+ key_files = scanner.find_key_files(root, extra_ignore=extra_ignore)
107
+ info["files"] = []
108
+ for rel_path, abs_path in key_files:
109
+ info["files"].append({
110
+ "path": rel_path,
111
+ "content": scanner.read_file(abs_path, max_lines=max_lines),
112
+ })
113
+
114
+ info["stats"] = build_stats(root, key_files)
115
+ return json.dumps(info, indent=2)
116
+
117
+
118
+ FORMATTERS = {
119
+ "markdown": format_markdown,
120
+ "plain": format_plain,
121
+ "json": format_json,
122
+ }
123
+
124
+
125
+ def merge_config_with_args(config, args):
126
+ if args.no_git is None and "git" in config:
127
+ args.no_git = not config["git"]
128
+ if args.max_files is None and "max_files" in config:
129
+ args.max_files = config["max_files"]
130
+ if args.max_lines is None and "max_lines" in config:
131
+ args.max_lines = config["max_lines"]
132
+ if args.format is None and "format" in config:
133
+ args.format = config["format"]
134
+ if args.exclude is None and "exclude" in config:
135
+ args.exclude = config["exclude"]
136
+ if args.output is None and "output" in config:
137
+ args.output = config["output"]
138
+ if args.hidden is None and "hidden" in config:
139
+ args.hidden = config["hidden"]
140
+ return args
141
+
142
+
143
+ def main():
144
+ import argparse
145
+ parser = argparse.ArgumentParser(
146
+ description="Extract codebase context for AI assistants.",
147
+ formatter_class=argparse.RawDescriptionHelpFormatter,
148
+ epilog=textwrap.dedent("""
149
+ Examples:
150
+ codecontext # full context (markdown)
151
+ codecontext --format json # JSON output
152
+ codecontext --clipboard # copy to clipboard
153
+ codecontext --exclude "*.log" "tmp/" # custom excludes
154
+ codecontext --hidden # include dotfiles
155
+ codecontext -o context.txt --format plain # plain text file
156
+ """),
157
+ )
158
+ parser.add_argument(
159
+ "path", nargs="?", default=None,
160
+ help="Project path (default: current directory)"
161
+ )
162
+ parser.add_argument(
163
+ "--no-git", action="store_true", default=None,
164
+ help="Skip git information"
165
+ )
166
+ parser.add_argument(
167
+ "--max-files", type=int, default=None,
168
+ help="Maximum files to show in tree (default: 200)"
169
+ )
170
+ parser.add_argument(
171
+ "--max-lines", type=int, default=None,
172
+ help="Maximum lines per file (default: 200)"
173
+ )
174
+ parser.add_argument(
175
+ "--format", "-f", default=None, choices=["markdown", "plain", "json"],
176
+ help="Output format (default: markdown)"
177
+ )
178
+ parser.add_argument(
179
+ "--clipboard", "-c", action="store_true",
180
+ help="Copy output to clipboard"
181
+ )
182
+ parser.add_argument(
183
+ "--output", "-o", default=None,
184
+ help="Write output to file"
185
+ )
186
+ parser.add_argument(
187
+ "--exclude", nargs="*", default=None,
188
+ help="Additional exclude patterns (glob)"
189
+ )
190
+ parser.add_argument(
191
+ "--hidden", action="store_true", default=None,
192
+ help="Show hidden files"
193
+ )
194
+ parser.add_argument(
195
+ "--quiet", "-q", action="store_true",
196
+ help="Suppress stderr messages"
197
+ )
198
+
199
+ args = parser.parse_args()
200
+
201
+ search_root = os.path.abspath(args.path or ".")
202
+ config = scanner.load_config(search_root)
203
+ args = merge_config_with_args(config, args)
204
+
205
+ if args.path is None:
206
+ args.path = "."
207
+
208
+ root = os.path.abspath(args.path)
209
+ if not os.path.isdir(root):
210
+ print(f"error: {root} is not a directory", file=sys.stderr)
211
+ sys.exit(1)
212
+
213
+ fmt = args.format or "markdown"
214
+ formatter = FORMATTERS.get(fmt, FORMATTERS["markdown"])
215
+
216
+ extra_ignore = args.exclude or []
217
+ context = formatter(
218
+ root,
219
+ include_git=not args.no_git if args.no_git is not None else True,
220
+ max_files=args.max_files or 200,
221
+ max_lines=args.max_lines or 200,
222
+ extra_ignore=extra_ignore,
223
+ show_hidden=args.hidden or False,
224
+ )
225
+
226
+ if args.clipboard:
227
+ try:
228
+ shutil.copy(context, None)
229
+ except Exception:
230
+ import subprocess
231
+ try:
232
+ proc = subprocess.Popen(["clip"], stdin=subprocess.PIPE, shell=True)
233
+ proc.communicate(input=context.encode("utf-8"))
234
+ if not args.quiet:
235
+ print("Copied to clipboard!", file=sys.stderr)
236
+ except Exception as e:
237
+ print(f"Clipboard error: {e}", file=sys.stderr)
238
+
239
+ if args.output:
240
+ with open(args.output, "w", encoding="utf-8") as f:
241
+ f.write(context)
242
+ if not args.quiet:
243
+ print(f"Written to {args.output}", file=sys.stderr)
244
+ else:
245
+ print(context)
246
+
247
+
248
+ if __name__ == "__main__":
249
+ main()
@@ -0,0 +1,47 @@
1
+ import subprocess
2
+ import os
3
+
4
+
5
+ def get_git_info(root):
6
+ info = {}
7
+ try:
8
+ result = subprocess.run(
9
+ ["git", "log", "--oneline", "-20"],
10
+ capture_output=True, text=True, cwd=root, timeout=10
11
+ )
12
+ if result.returncode == 0:
13
+ info["recent_commits"] = result.stdout.strip()
14
+ except Exception:
15
+ info["recent_commits"] = ""
16
+
17
+ try:
18
+ result = subprocess.run(
19
+ ["git", "remote", "get-url", "origin"],
20
+ capture_output=True, text=True, cwd=root, timeout=5
21
+ )
22
+ if result.returncode == 0:
23
+ info["remote"] = result.stdout.strip()
24
+ except Exception:
25
+ info["remote"] = ""
26
+
27
+ try:
28
+ result = subprocess.run(
29
+ ["git", "branch", "--show-current"],
30
+ capture_output=True, text=True, cwd=root, timeout=5
31
+ )
32
+ if result.returncode == 0:
33
+ info["branch"] = result.stdout.strip()
34
+ except Exception:
35
+ info["branch"] = ""
36
+
37
+ try:
38
+ result = subprocess.run(
39
+ ["git", "diff", "--stat"],
40
+ capture_output=True, text=True, cwd=root, timeout=5
41
+ )
42
+ if result.returncode == 0:
43
+ info["uncommitted"] = result.stdout.strip()
44
+ except Exception:
45
+ info["uncommitted"] = ""
46
+
47
+ return info
@@ -0,0 +1,156 @@
1
+ import os
2
+ import fnmatch
3
+
4
+
5
+ IGNORE_PATTERNS = {
6
+ ".git", "__pycache__", "node_modules", ".venv", "venv", "env",
7
+ ".tox", ".eggs", "*.egg-info", "dist", "build", ".next",
8
+ ".nuxt", "target", "bin", "obj", ".idea", ".vscode",
9
+ "*.pyc", "*.pyo", "*.so", "*.dll", "*.dylib",
10
+ "*.png", "*.jpg", "*.jpeg", "*.gif", "*.svg", "*.ico",
11
+ "*.ttf", "*.woff", "*.woff2", "*.eot",
12
+ "*.zip", "*.tar", "*.gz", "*.7z", "*.rar",
13
+ ".DS_Store", "Thumbs.db", "*.lock", "package-lock.json",
14
+ "*.min.js", "*.min.css", "*.map",
15
+ ".coverage", ".pytest_cache", ".mypy_cache", ".ruff_cache",
16
+ }
17
+
18
+ BINARY_EXTENSIONS = {
19
+ ".png", ".jpg", ".jpeg", ".gif", ".ico", ".svg",
20
+ ".ttf", ".woff", ".woff2", ".eot",
21
+ ".zip", ".tar", ".gz", ".7z", ".rar",
22
+ ".so", ".dll", ".dylib", ".exe", ".bin",
23
+ ".pyc", ".pyo",
24
+ ".pdf", ".doc", ".docx", ".xls", ".xlsx",
25
+ ".mp3", ".mp4", ".avi", ".mov",
26
+ ".woff2", ".woff", ".eot",
27
+ ".map", ".min.js", ".min.css",
28
+ }
29
+
30
+ CONFIG_FILENAMES = [".codecontext.toml", ".codecontext.json", ".codecontext.yml", ".codecontext.yaml"]
31
+
32
+
33
+ def should_ignore(name, extra_patterns=None):
34
+ if any(fnmatch.fnmatch(name, p) for p in IGNORE_PATTERNS):
35
+ return True
36
+ if extra_patterns:
37
+ for p in extra_patterns:
38
+ if fnmatch.fnmatch(name, p):
39
+ return True
40
+ return False
41
+
42
+
43
+ def _format_size(size):
44
+ if size < 1024:
45
+ return f"{size}B"
46
+ elif size < 1024 ** 2:
47
+ return f"{size / 1024:.0f}KB"
48
+ elif size < 1024 ** 3:
49
+ return f"{size / 1024 ** 2:.1f}MB"
50
+ return f"{size / 1024 ** 3:.1f}GB"
51
+
52
+
53
+ def scan_tree(root, prefix="", max_files=200, _count=None, extra_ignore=None, show_hidden=False):
54
+ if _count is None:
55
+ _count = [0]
56
+ if _count[0] >= max_files:
57
+ return []
58
+ lines = []
59
+ try:
60
+ entries = sorted(os.listdir(root))
61
+ except PermissionError:
62
+ return lines
63
+
64
+ entries = [e for e in entries if not should_ignore(e, extra_ignore)]
65
+ if not show_hidden:
66
+ entries = [e for e in entries if not e.startswith(".") or e in (".gitignore", ".env.example")]
67
+
68
+ for i, name in enumerate(entries):
69
+ if _count[0] >= max_files:
70
+ break
71
+ path = os.path.join(root, name)
72
+ is_last = i == len(entries) - 1
73
+ connector = "└── " if is_last else "├── "
74
+
75
+ if os.path.isdir(path):
76
+ lines.append(f"{prefix}{connector}{name}/")
77
+ _count[0] += 1
78
+ extension = " " if is_last else "│ "
79
+ lines.extend(scan_tree(path, prefix + extension, max_files, _count, extra_ignore, show_hidden))
80
+ elif os.path.isfile(path):
81
+ try:
82
+ size = os.path.getsize(path)
83
+ size_str = _format_size(size)
84
+ except OSError:
85
+ size_str = "?"
86
+ lines.append(f"{prefix}{connector}{name} ({size_str})")
87
+ _count[0] += 1
88
+ return lines
89
+
90
+
91
+ def get_tree(root, max_files=200, extra_ignore=None, show_hidden=False):
92
+ name = os.path.basename(os.path.abspath(root))
93
+ lines = [f"{name}/"]
94
+ lines.extend(scan_tree(root, max_files=max_files, extra_ignore=extra_ignore, show_hidden=show_hidden))
95
+ return "\n".join(lines)
96
+
97
+
98
+ def find_key_files(root, max_size=50_000, extra_ignore=None):
99
+ key_files = []
100
+ for dirpath, dirnames, filenames in os.walk(root):
101
+ dirnames[:] = [d for d in dirnames if not should_ignore(d, extra_ignore)]
102
+ for f in filenames:
103
+ if should_ignore(f, extra_ignore):
104
+ continue
105
+ path = os.path.join(dirpath, f)
106
+ ext = os.path.splitext(f)[1].lower()
107
+ if ext in BINARY_EXTENSIONS:
108
+ continue
109
+ try:
110
+ size = os.path.getsize(path)
111
+ except OSError:
112
+ continue
113
+ if size > max_size:
114
+ continue
115
+ rel = os.path.relpath(path, root)
116
+ key_files.append((rel, path))
117
+ key_files.sort()
118
+ return key_files
119
+
120
+
121
+ def read_file(path, max_lines=200):
122
+ try:
123
+ with open(path, "r", encoding="utf-8", errors="replace") as f:
124
+ lines = f.readlines()
125
+ if len(lines) > max_lines:
126
+ lines = lines[:max_lines]
127
+ lines.append(f"... ({len(lines)} lines shown, truncated)\n")
128
+ return "".join(lines)
129
+ except Exception as e:
130
+ return f"// Error reading file: {e}\n"
131
+
132
+
133
+ def load_config(root):
134
+ for filename in CONFIG_FILENAMES:
135
+ path = os.path.join(root, filename)
136
+ if not os.path.isfile(path):
137
+ continue
138
+ try:
139
+ with open(path, "r", encoding="utf-8") as f:
140
+ raw = f.read()
141
+ if filename.endswith(".toml"):
142
+ try:
143
+ import tomllib
144
+ return tomllib.loads(raw)
145
+ except ImportError:
146
+ import tomli as tomllib
147
+ return tomllib.loads(raw)
148
+ elif filename.endswith(".json"):
149
+ import json
150
+ return json.loads(raw)
151
+ elif filename.endswith((".yml", ".yaml")):
152
+ import yaml
153
+ return yaml.safe_load(raw)
154
+ except Exception:
155
+ pass
156
+ return {}