ai-pack-cli 0.1.0__tar.gz → 0.1.2__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,153 @@
1
+ Metadata-Version: 2.1
2
+ Name: ai-pack-cli
3
+ Version: 0.1.2
4
+ Home-page: https://github.com/iamraydoan/ai-pack
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Operating System :: OS Independent
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: pyperclip>=1.8.2
10
+ Requires-Dist: questionary>=1.10.0
11
+
12
+ # 📦 ai-pack
13
+
14
+ [![Python Version](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://www.python.org/)
15
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
16
+ [![PyPI version](https://badge.fury.io/py/ai-pack-cli.svg)](https://badge.fury.io/py/ai-pack-cli)
17
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-orange.svg)](https://github.com/)
18
+ [![GitHub Stars](https://img.shields.io/github/stars/iamraydoan/ai-pack.svg?style=social)](https://github.com/iamraydoan/ai-pack)
19
+
20
+ > Pack your entire codebase into a single formatted Markdown prompt for LLMs, optimized for minimum tokens.
21
+
22
+ `ai-pack` is a lightweight, high-performance CLI tool designed to help developers package their codebase, specific files, or uncommitted changes into a clean Markdown payload. Easily copy it to the clipboard or save it to a file to feed directly into ChatGPT, Claude, Gemini, or any other LLM.
23
+
24
+ ---
25
+
26
+ ## 🔥 Key Features
27
+
28
+ * **⚡ Native CLI Command**: Accessible globally as `ai-pack`, `aipack`, or `aip`.
29
+ * **💀 Code Skeleton Extraction (`--skeleton`)**: Drastically save tokens by stripping method and function bodies, keeping only class structures, imports, signatures, and docstrings.
30
+ * **🎯 Interactive Selection (`-i`)**: Interactively choose which files to pack using Arrow keys and Spacebar before generating the payload.
31
+ * **🌿 Git-Aware (`--changed`)**: Automatically detect and pack only modified, staged, or untracked files.
32
+ * **🛡️ Gitignore Respecting**: Native Git integration using `git ls-files` to automatically ignore build artifacts, node modules, and everything in `.gitignore` (with a clean manual fallback for non-git folders).
33
+ * **💬 Predefined LLM Prompts (`-p`)**: Instantly prepend pre-configured prompts for code review, bug hunting, or architecture explanations.
34
+ * **📊 Token Estimation**: Heuristic token counting warns you if your payload exceeds your limit (`--max-tokens`).
35
+
36
+ ---
37
+
38
+ ## 🚀 Quick Start
39
+
40
+ ### Installation
41
+
42
+ Choose one of the following methods to get started quickly:
43
+
44
+ #### Option 1: Install from PyPI (Recommended)
45
+ Install the official release from PyPI:
46
+ ```bash
47
+ pip3 install ai-pack-cli --user
48
+ ```
49
+ Or via `pipx` to run in an isolated environment:
50
+ ```bash
51
+ pipx install ai-pack-cli
52
+ ```
53
+
54
+ #### Option 2: Install directly from GitHub
55
+ Install the latest cutting-edge development version directly:
56
+ ```bash
57
+ pip3 install git+https://github.com/iamraydoan/ai-pack.git --user
58
+ ```
59
+
60
+ #### Option 3: Manual Clone (Editable mode)
61
+ If you want to modify the source code:
62
+ ```bash
63
+ git clone https://github.com/iamraydoan/ai-pack.git
64
+ cd ai-pack
65
+ pip3 install -e . --user
66
+ ```
67
+
68
+ #### Option 4: Run as a Standalone Script (One-Liner)
69
+ Since `ai-pack` is a self-contained single script, you can download it directly:
70
+ ```bash
71
+ curl -o ~/.local/bin/aip https://raw.githubusercontent.com/iamraydoan/ai-pack/main/ai_pack.py && chmod +x ~/.local/bin/aip
72
+ ```
73
+ *(Note: If you run it standalone, you will need to manually run `pip3 install pyperclip questionary` to enable all optional interactive and clipboard features).*
74
+
75
+
76
+ ### Usage Examples
77
+
78
+ #### 1. Pack the entire repository (copied to clipboard)
79
+ ```bash
80
+ aip
81
+ ```
82
+
83
+ #### 2. Pack specific files and save to a file
84
+ ```bash
85
+ aip -f src/main.py tests/ -o output.md
86
+ ```
87
+
88
+ #### 3. Pack only uncommitted git changes with a code review prompt
89
+ ```bash
90
+ aip --changed -p review
91
+ ```
92
+
93
+ #### 4. Extract code skeleton only (drastically saves context window tokens)
94
+ ```bash
95
+ aip --skeleton -p explain
96
+ ```
97
+
98
+ #### 5. Interactively choose files to include
99
+ ```bash
100
+ aip -i
101
+ ```
102
+
103
+ ---
104
+
105
+ ## 💀 Skeleton Mode Demo
106
+
107
+ ### Original Code (`math.py`):
108
+ ```python
109
+ def fibonacci(n):
110
+ if n <= 0:
111
+ return []
112
+ elif n == 1:
113
+ return [0]
114
+ sequence = [0, 1]
115
+ while len(sequence) < n:
116
+ sequence.append(sequence[-1] + sequence[-2])
117
+ return sequence
118
+ ```
119
+
120
+ ### Skeleton Output:
121
+ ```python
122
+ def fibonacci(n):
123
+ ...
124
+ ```
125
+
126
+ *Supports Python and all brace-delimited languages (JS, TS, Go, Rust, C++, Java, Swift, etc.)*
127
+
128
+ ---
129
+
130
+ ## 🎨 Interactive CLI Checklist
131
+
132
+ Running `aip -i` triggers a beautiful checkbox prompt:
133
+
134
+ ```text
135
+ ? Select files to pack (Space to toggle, Enter to confirm):
136
+ ❯ [x] src/main.py
137
+ [x] src/utils.py
138
+ [ ] tests/test_main.py
139
+ ```
140
+
141
+ ---
142
+
143
+ ## 🤝 Contributing
144
+
145
+ Contributions are welcome! If you have ideas for new features, bug fixes, or enhancements:
146
+
147
+ 1. Fork the repo.
148
+ 2. Create your feature branch (`git checkout -b feat/amazing-feature`).
149
+ 3. Commit your changes (`git commit -m 'feat: add amazing feature'`).
150
+ 4. Push to the branch (`git push origin feat/amazing-feature`).
151
+ 5. Open a Pull Request.
152
+
153
+ **Don't forget to give the project a ⭐ if you found it useful!**
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![Python Version](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://www.python.org/)
4
4
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
5
+ [![PyPI version](https://badge.fury.io/py/ai-pack-cli.svg)](https://badge.fury.io/py/ai-pack-cli)
5
6
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-orange.svg)](https://github.com/)
6
7
  [![GitHub Stars](https://img.shields.io/github/stars/iamraydoan/ai-pack.svg?style=social)](https://github.com/iamraydoan/ai-pack)
7
8
 
@@ -29,16 +30,20 @@
29
30
 
30
31
  Choose one of the following methods to get started quickly:
31
32
 
32
- #### Option 1: Install directly from GitHub (Recommended)
33
- You can install the tool directly without cloning the repo manually:
33
+ #### Option 1: Install from PyPI (Recommended)
34
+ Install the official release from PyPI:
34
35
  ```bash
35
- pip3 install git+https://github.com/iamraydoan/ai-pack.git --user
36
+ pip3 install ai-pack-cli --user
37
+ ```
38
+ Or via `pipx` to run in an isolated environment:
39
+ ```bash
40
+ pipx install ai-pack-cli
36
41
  ```
37
42
 
38
- #### Option 2: Using `pipx` (Best for isolated CLI tools)
39
- Ensure you don't run into dependency conflicts by running:
43
+ #### Option 2: Install directly from GitHub
44
+ Install the latest cutting-edge development version directly:
40
45
  ```bash
41
- pipx install git+https://github.com/iamraydoan/ai-pack.git
46
+ pip3 install git+https://github.com/iamraydoan/ai-pack.git --user
42
47
  ```
43
48
 
44
49
  #### Option 3: Manual Clone (Editable mode)
@@ -231,10 +231,16 @@ class SkeletonExtractor:
231
231
  indent = len(line) - len(line.lstrip())
232
232
  sig_lines = []
233
233
 
234
- # Consume multi-line signatures
234
+ # Consume multi-line signatures by tracking open parentheses/brackets
235
+ open_braces = 0
235
236
  while i < n:
236
- sig_lines.append(lines[i])
237
- if lines[i].rstrip().endswith(":"):
237
+ line_to_add = lines[i]
238
+ sig_lines.append(line_to_add)
239
+ open_braces += line_to_add.count('(') - line_to_add.count(')')
240
+ open_braces += line_to_add.count('[') - line_to_add.count(']')
241
+ open_braces += line_to_add.count('{') - line_to_add.count('}')
242
+
243
+ if open_braces <= 0 and ":" in line_to_add:
238
244
  break
239
245
  i += 1
240
246
 
@@ -360,15 +366,18 @@ class SkeletonExtractor:
360
366
  is_func = True
361
367
  elif any(x in prefix_str for x in ('fn ', 'func ')):
362
368
  is_func = True
363
- elif prefix_str.endswith(')'):
364
- words = [w for w in prefix_str.split() if w]
365
- clean_words = [w for w in words if w not in (
366
- 'export', 'async', 'default', 'public', 'private', 'protected', 'static', 'readonly'
367
- )]
368
- if clean_words:
369
- first_word = clean_words[0].split('(')[0].strip()
370
- if first_word not in ('if', 'for', 'while', 'switch', 'catch'):
371
- is_func = True
369
+ elif '(' in prefix_str and ')' in prefix_str:
370
+ first_paren = prefix_str.find('(')
371
+ name_part = prefix_str[:first_paren].strip()
372
+ words = name_part.split()
373
+ if words:
374
+ clean_words = [w for w in words if w not in (
375
+ 'export', 'async', 'default', 'public', 'private', 'protected', 'static', 'readonly'
376
+ )]
377
+ if clean_words:
378
+ first_word = clean_words[0].strip()
379
+ if first_word not in ('if', 'for', 'while', 'switch', 'catch'):
380
+ is_func = True
372
381
 
373
382
  if is_func:
374
383
  skip_until_brace_depth = brace_depth
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.1
2
+ Name: ai-pack-cli
3
+ Version: 0.1.2
4
+ Home-page: https://github.com/iamraydoan/ai-pack
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Operating System :: OS Independent
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: pyperclip>=1.8.2
10
+ Requires-Dist: questionary>=1.10.0
11
+
12
+ # 📦 ai-pack
13
+
14
+ [![Python Version](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://www.python.org/)
15
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
16
+ [![PyPI version](https://badge.fury.io/py/ai-pack-cli.svg)](https://badge.fury.io/py/ai-pack-cli)
17
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-orange.svg)](https://github.com/)
18
+ [![GitHub Stars](https://img.shields.io/github/stars/iamraydoan/ai-pack.svg?style=social)](https://github.com/iamraydoan/ai-pack)
19
+
20
+ > Pack your entire codebase into a single formatted Markdown prompt for LLMs, optimized for minimum tokens.
21
+
22
+ `ai-pack` is a lightweight, high-performance CLI tool designed to help developers package their codebase, specific files, or uncommitted changes into a clean Markdown payload. Easily copy it to the clipboard or save it to a file to feed directly into ChatGPT, Claude, Gemini, or any other LLM.
23
+
24
+ ---
25
+
26
+ ## 🔥 Key Features
27
+
28
+ * **⚡ Native CLI Command**: Accessible globally as `ai-pack`, `aipack`, or `aip`.
29
+ * **💀 Code Skeleton Extraction (`--skeleton`)**: Drastically save tokens by stripping method and function bodies, keeping only class structures, imports, signatures, and docstrings.
30
+ * **🎯 Interactive Selection (`-i`)**: Interactively choose which files to pack using Arrow keys and Spacebar before generating the payload.
31
+ * **🌿 Git-Aware (`--changed`)**: Automatically detect and pack only modified, staged, or untracked files.
32
+ * **🛡️ Gitignore Respecting**: Native Git integration using `git ls-files` to automatically ignore build artifacts, node modules, and everything in `.gitignore` (with a clean manual fallback for non-git folders).
33
+ * **💬 Predefined LLM Prompts (`-p`)**: Instantly prepend pre-configured prompts for code review, bug hunting, or architecture explanations.
34
+ * **📊 Token Estimation**: Heuristic token counting warns you if your payload exceeds your limit (`--max-tokens`).
35
+
36
+ ---
37
+
38
+ ## 🚀 Quick Start
39
+
40
+ ### Installation
41
+
42
+ Choose one of the following methods to get started quickly:
43
+
44
+ #### Option 1: Install from PyPI (Recommended)
45
+ Install the official release from PyPI:
46
+ ```bash
47
+ pip3 install ai-pack-cli --user
48
+ ```
49
+ Or via `pipx` to run in an isolated environment:
50
+ ```bash
51
+ pipx install ai-pack-cli
52
+ ```
53
+
54
+ #### Option 2: Install directly from GitHub
55
+ Install the latest cutting-edge development version directly:
56
+ ```bash
57
+ pip3 install git+https://github.com/iamraydoan/ai-pack.git --user
58
+ ```
59
+
60
+ #### Option 3: Manual Clone (Editable mode)
61
+ If you want to modify the source code:
62
+ ```bash
63
+ git clone https://github.com/iamraydoan/ai-pack.git
64
+ cd ai-pack
65
+ pip3 install -e . --user
66
+ ```
67
+
68
+ #### Option 4: Run as a Standalone Script (One-Liner)
69
+ Since `ai-pack` is a self-contained single script, you can download it directly:
70
+ ```bash
71
+ curl -o ~/.local/bin/aip https://raw.githubusercontent.com/iamraydoan/ai-pack/main/ai_pack.py && chmod +x ~/.local/bin/aip
72
+ ```
73
+ *(Note: If you run it standalone, you will need to manually run `pip3 install pyperclip questionary` to enable all optional interactive and clipboard features).*
74
+
75
+
76
+ ### Usage Examples
77
+
78
+ #### 1. Pack the entire repository (copied to clipboard)
79
+ ```bash
80
+ aip
81
+ ```
82
+
83
+ #### 2. Pack specific files and save to a file
84
+ ```bash
85
+ aip -f src/main.py tests/ -o output.md
86
+ ```
87
+
88
+ #### 3. Pack only uncommitted git changes with a code review prompt
89
+ ```bash
90
+ aip --changed -p review
91
+ ```
92
+
93
+ #### 4. Extract code skeleton only (drastically saves context window tokens)
94
+ ```bash
95
+ aip --skeleton -p explain
96
+ ```
97
+
98
+ #### 5. Interactively choose files to include
99
+ ```bash
100
+ aip -i
101
+ ```
102
+
103
+ ---
104
+
105
+ ## 💀 Skeleton Mode Demo
106
+
107
+ ### Original Code (`math.py`):
108
+ ```python
109
+ def fibonacci(n):
110
+ if n <= 0:
111
+ return []
112
+ elif n == 1:
113
+ return [0]
114
+ sequence = [0, 1]
115
+ while len(sequence) < n:
116
+ sequence.append(sequence[-1] + sequence[-2])
117
+ return sequence
118
+ ```
119
+
120
+ ### Skeleton Output:
121
+ ```python
122
+ def fibonacci(n):
123
+ ...
124
+ ```
125
+
126
+ *Supports Python and all brace-delimited languages (JS, TS, Go, Rust, C++, Java, Swift, etc.)*
127
+
128
+ ---
129
+
130
+ ## 🎨 Interactive CLI Checklist
131
+
132
+ Running `aip -i` triggers a beautiful checkbox prompt:
133
+
134
+ ```text
135
+ ? Select files to pack (Space to toggle, Enter to confirm):
136
+ ❯ [x] src/main.py
137
+ [x] src/utils.py
138
+ [ ] tests/test_main.py
139
+ ```
140
+
141
+ ---
142
+
143
+ ## 🤝 Contributing
144
+
145
+ Contributions are welcome! If you have ideas for new features, bug fixes, or enhancements:
146
+
147
+ 1. Fork the repo.
148
+ 2. Create your feature branch (`git checkout -b feat/amazing-feature`).
149
+ 3. Commit your changes (`git commit -m 'feat: add amazing feature'`).
150
+ 4. Push to the branch (`git push origin feat/amazing-feature`).
151
+ 5. Open a Pull Request.
152
+
153
+ **Don't forget to give the project a ⭐ if you found it useful!**
@@ -6,4 +6,8 @@ ai_pack_cli.egg-info/SOURCES.txt
6
6
  ai_pack_cli.egg-info/dependency_links.txt
7
7
  ai_pack_cli.egg-info/entry_points.txt
8
8
  ai_pack_cli.egg-info/requires.txt
9
- ai_pack_cli.egg-info/top_level.txt
9
+ ai_pack_cli.egg-info/top_level.txt
10
+ tests/test_binary.py
11
+ tests/test_gitignore.py
12
+ tests/test_skeleton_braces.py
13
+ tests/test_skeleton_python.py
@@ -0,0 +1,30 @@
1
+ from setuptools import setup
2
+
3
+ # Read the contents of README.md to use as the long description on PyPI
4
+ with open("README.md", "r", encoding="utf-8") as fh:
5
+ long_description = fh.read()
6
+
7
+ setup(
8
+ name="ai-pack-cli",
9
+ version="0.1.2",
10
+ py_modules=["ai_pack"],
11
+ install_requires=[
12
+ "pyperclip>=1.8.2",
13
+ "questionary>=1.10.0",
14
+ ],
15
+ entry_points={
16
+ "console_scripts": [
17
+ "ai-pack=ai_pack:main",
18
+ "aipack=ai_pack:main",
19
+ "aip=ai_pack:main",
20
+ ],
21
+ },
22
+ long_description=long_description,
23
+ long_description_content_type="text/markdown",
24
+ url="https://github.com/iamraydoan/ai-pack",
25
+ classifiers=[
26
+ "Programming Language :: Python :: 3",
27
+ "License :: OSI Approved :: MIT License",
28
+ "Operating System :: OS Independent",
29
+ ],
30
+ )
@@ -0,0 +1,33 @@
1
+ import unittest
2
+ import os
3
+ import tempfile
4
+ import shutil
5
+ from ai_pack import is_binary
6
+
7
+ class TestBinaryDetection(unittest.TestCase):
8
+ def setUp(self):
9
+ self.test_dir = tempfile.mkdtemp()
10
+
11
+ def tearDown(self):
12
+ shutil.rmtree(self.test_dir)
13
+
14
+ def test_text_file_is_not_binary(self):
15
+ file_path = os.path.join(self.test_dir, "text.txt")
16
+ with open(file_path, "w", encoding="utf-8") as f:
17
+ f.write("Hello, this is a plain text file with normal characters.")
18
+ self.assertFalse(is_binary(file_path))
19
+
20
+ def test_empty_file_is_not_binary(self):
21
+ file_path = os.path.join(self.test_dir, "empty.txt")
22
+ with open(file_path, "w") as f:
23
+ f.write("")
24
+ self.assertFalse(is_binary(file_path))
25
+
26
+ def test_binary_file_with_null_byte_is_binary(self):
27
+ file_path = os.path.join(self.test_dir, "binary.bin")
28
+ with open(file_path, "wb") as f:
29
+ f.write(b"PNG\x00\x00\x00\r\nIHDR")
30
+ self.assertTrue(is_binary(file_path))
31
+
32
+ def test_missing_file_is_treated_as_binary(self):
33
+ self.assertTrue(is_binary(os.path.join(self.test_dir, "does_not_exist.bin")))
@@ -0,0 +1,38 @@
1
+ import unittest
2
+ import os
3
+ import tempfile
4
+ import shutil
5
+ from ai_pack import GitignoreMatcher
6
+
7
+ class TestGitignoreMatcher(unittest.TestCase):
8
+ def setUp(self):
9
+ self.test_dir = tempfile.mkdtemp()
10
+
11
+ def tearDown(self):
12
+ shutil.rmtree(self.test_dir)
13
+
14
+ def test_standard_ignores(self):
15
+ matcher = GitignoreMatcher(self.test_dir)
16
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, "node_modules", "lodash", "index.js")))
17
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, "src", "__pycache__", "utils.cpython-38.pyc")))
18
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, ".git", "config")))
19
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, "venv", "lib", "python3.8", "site-packages")))
20
+
21
+ def test_matching_wildcard_patterns(self):
22
+ with open(os.path.join(self.test_dir, ".gitignore"), "w") as f:
23
+ f.write("*.log\ntemp*\n")
24
+ matcher = GitignoreMatcher(self.test_dir)
25
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, "error.log")))
26
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, "sub", "output.log")))
27
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, "temp_data.csv")))
28
+ self.assertFalse(matcher.is_ignored(os.path.join(self.test_dir, "item_temp.txt")))
29
+
30
+ def test_absolute_vs_relative_gitignore_paths(self):
31
+ with open(os.path.join(self.test_dir, ".gitignore"), "w") as f:
32
+ f.write("/root_only.txt\nsub/ignored.txt\n")
33
+ matcher = GitignoreMatcher(self.test_dir)
34
+
35
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, "root_only.txt")))
36
+ self.assertFalse(matcher.is_ignored(os.path.join(self.test_dir, "nested", "root_only.txt")))
37
+
38
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, "sub", "ignored.txt")))
@@ -0,0 +1,139 @@
1
+ import unittest
2
+ from ai_pack import SkeletonExtractor
3
+
4
+ class TestBraceSkeletonExtractor(unittest.TestCase):
5
+ def test_javascript_typescript(self):
6
+ js_code = """import { component } from 'framework';
7
+ const PORT = 8080;
8
+
9
+ export default class Widget extends Base {
10
+ constructor(config) {
11
+ super(config);
12
+ this.init();
13
+ }
14
+
15
+ render() {
16
+ return `<div>${this.config.title}</div>`;
17
+ }
18
+ }
19
+
20
+ const add = (a, b) => {
21
+ return a + b;
22
+ };
23
+ """
24
+ expected = """import { component } from 'framework';
25
+ const PORT = 8080;
26
+
27
+ export default class Widget extends Base {
28
+ constructor(config) { /* ... */ }
29
+
30
+ render() { /* ... */ }
31
+ }
32
+
33
+ const add = (a, b) => { /* ... */ };"""
34
+ self.assertEqual(SkeletonExtractor.extract_brace_skeleton(js_code).strip(), expected.strip())
35
+
36
+ def test_js_template_literals_with_braces(self):
37
+ js_code = """const render = (name) => {
38
+ return `Hello, ${name}! Your age is ${getAge({birth: 1990})}.`;
39
+ };
40
+ """
41
+ expected = """const render = (name) => { /* ... */ };"""
42
+ self.assertEqual(SkeletonExtractor.extract_brace_skeleton(js_code).strip(), expected.strip())
43
+
44
+ def test_golang_parser(self):
45
+ go_code = """package main
46
+ import "fmt"
47
+
48
+ type Server struct {
49
+ Port int
50
+ }
51
+
52
+ func NewServer(port int) *Server {
53
+ return &Server{Port: port}
54
+ }
55
+
56
+ func (s *Server) Start() error {
57
+ fmt.Println("Starting...")
58
+ return nil
59
+ }
60
+ """
61
+ expected = """package main
62
+ import "fmt"
63
+
64
+ type Server struct {
65
+ Port int
66
+ }
67
+
68
+ func NewServer(port int) *Server { /* ... */ }
69
+
70
+ func (s *Server) Start() error { /* ... */ }"""
71
+ self.assertEqual(SkeletonExtractor.extract_brace_skeleton(go_code).strip(), expected.strip())
72
+
73
+ def test_rust_parser(self):
74
+ rust_code = """use std::io;
75
+
76
+ pub struct Robot {
77
+ name: String,
78
+ }
79
+
80
+ impl Robot {
81
+ pub fn new(name: &str) -> Self {
82
+ Robot {
83
+ name: name.to_string(),
84
+ }
85
+ }
86
+
87
+ fn speak(&self) {
88
+ println!("Hello, my name is {}", self.name);
89
+ }
90
+ }
91
+ """
92
+ expected = """use std::io;
93
+
94
+ pub struct Robot {
95
+ name: String,
96
+ }
97
+
98
+ impl Robot {
99
+ pub fn new(name: &str) -> Self { /* ... */ }
100
+
101
+ fn speak(&self) { /* ... */ }
102
+ }"""
103
+ self.assertEqual(SkeletonExtractor.extract_brace_skeleton(rust_code).strip(), expected.strip())
104
+
105
+ def test_cpp_java_parser(self):
106
+ java_code = """public class App {
107
+ public static void main(String[] args) {
108
+ System.out.println("Hello World!");
109
+ }
110
+
111
+ private int calc(int a) {
112
+ return a * 2;
113
+ }
114
+ }
115
+ """
116
+ expected = """public class App {
117
+ public static void main(String[] args) { /* ... */ }
118
+
119
+ private int calc(int a) { /* ... */ }
120
+ }"""
121
+ self.assertEqual(SkeletonExtractor.extract_brace_skeleton(java_code).strip(), expected.strip())
122
+
123
+ def test_typescript_return_types(self):
124
+ ts_code = """class Manager {
125
+ async getItems(req: Request): Promise<Item[]> {
126
+ return this.client.get();
127
+ }
128
+
129
+ add(a: number, b: number): number {
130
+ return a + b;
131
+ }
132
+ }
133
+ """
134
+ expected = """class Manager {
135
+ async getItems(req: Request): Promise<Item[]> { /* ... */ }
136
+
137
+ add(a: number, b: number): number { /* ... */ }
138
+ }"""
139
+ self.assertEqual(SkeletonExtractor.extract_brace_skeleton(ts_code).strip(), expected.strip())
@@ -0,0 +1,63 @@
1
+ import unittest
2
+ from ai_pack import SkeletonExtractor
3
+
4
+ class TestPythonSkeletonExtractor(unittest.TestCase):
5
+ def test_basic_functions_and_classes(self):
6
+ code = """class User:
7
+ def __init__(self, name):
8
+ self.name = name
9
+ def greet(self):
10
+ print("Hello " + self.name)
11
+ """
12
+ expected = """class User:
13
+ def __init__(self, name):
14
+ ...
15
+ def greet(self):
16
+ ..."""
17
+ self.assertEqual(SkeletonExtractor.extract_python_skeleton(code).strip(), expected.strip())
18
+
19
+ def test_decorators_and_async(self):
20
+ code = """@property
21
+ @cache
22
+ def value(self):
23
+ return self._val
24
+
25
+ async def fetch_data(url):
26
+ res = await client.get(url)
27
+ return res.json()
28
+ """
29
+ expected = """@property
30
+ @cache
31
+ def value(self):
32
+ ...
33
+ async def fetch_data(url):
34
+ ..."""
35
+ self.assertEqual(SkeletonExtractor.extract_python_skeleton(code).strip(), expected.strip())
36
+
37
+ def test_multiline_signatures(self):
38
+ code = """def complex_function(
39
+ a: int,
40
+ b: str,
41
+ c: float = 1.0
42
+ ) -> bool:
43
+ print(a, b, c)
44
+ return True
45
+ """
46
+ expected = """def complex_function(
47
+ a: int,
48
+ b: str,
49
+ c: float = 1.0
50
+ ) -> bool:
51
+ ..."""
52
+ self.assertEqual(SkeletonExtractor.extract_python_skeleton(code).strip(), expected.strip())
53
+
54
+ def test_single_line_and_comments(self):
55
+ code = """def simple_get(self): return self.x
56
+
57
+ def comment_sig(a, b): # basic add
58
+ return a + b
59
+ """
60
+ expected = """def simple_get(self): return self.x
61
+ def comment_sig(a, b): # basic add
62
+ ..."""
63
+ self.assertEqual(SkeletonExtractor.extract_python_skeleton(code).strip(), expected.strip())
@@ -1,5 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: ai-pack-cli
3
- Version: 0.1.0
4
- Requires-Dist: pyperclip>=1.8.2
5
- Requires-Dist: questionary>=1.10.0
@@ -1,5 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: ai-pack-cli
3
- Version: 0.1.0
4
- Requires-Dist: pyperclip>=1.8.2
5
- Requires-Dist: questionary>=1.10.0
@@ -1,18 +0,0 @@
1
- from setuptools import setup
2
-
3
- setup(
4
- name="ai-pack-cli",
5
- version="0.1.0",
6
- py_modules=["ai_pack"],
7
- install_requires=[
8
- "pyperclip>=1.8.2",
9
- "questionary>=1.10.0",
10
- ],
11
- entry_points={
12
- "console_scripts": [
13
- "ai-pack=ai_pack:main",
14
- "aipack=ai_pack:main",
15
- "aip=ai_pack:main",
16
- ],
17
- },
18
- )
File without changes