├── .gitignore
├── LICENSE
├── README.md
├── codex_readme.py
├── requirements.txt
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Tom Dörr
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
🤖 codex-readme 📜
2 |
3 |
4 |
5 | The gif shows how the readme below was generated. To generate your own readmes you need to get access to the Codex API (https://openai.com/blog/openai-codex/).
6 |
7 |
8 |
9 |
10 | # codex-readme
11 | ## What is it?
12 |
13 | This project is a set of programs that I use to create a README.md file.
14 |
15 | ## How does it work?
16 |
17 | It reads program files and concatenates the beginning of all files to create a input prompt which is then fed to OpenAI Codex to generate a README.
18 |
19 | ## How to use it?
20 |
21 | ```
22 | ./codex_readme.py
23 | ```
24 |
25 | ## TODO
26 |
27 | - [ ] Add more programs and improve the README.md generator.
28 |
29 | ## License
30 |
31 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
32 |
33 | ## Credits
34 |
35 | This project is based on the OpenAI Codex project.
36 |
37 | ## Contact
38 |
39 | If you have any questions or would like to get in touch, please open an issue on Github or send me an email:
40 |
41 | -------------------------------------------------------------------
42 |
43 | [Traffic Statistics](https://tom-doerr.github.io/github_repo_stats_data/tom-doerr/codex-readme/latest-report/report.html)
44 |
--------------------------------------------------------------------------------
/codex_readme.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | '''
4 | This script reads program files and concatenates the beginning of
5 | all files to create a input prompt which is then fed to OpenAI
6 | Codex to generate a README.
7 | '''
8 | import sys
9 |
10 | # Check if the openai module is installed.
11 | try:
12 | import openai
13 | except ImportError:
14 | print('openai module not found. Try running "pip3 install openai"')
15 | sys.exit(1)
16 |
17 | import os
18 | import argparse
19 | import configparser
20 |
21 | FILES_NOT_TO_INCLUDE = ['LICENSE', 'README.md']
22 | STREAM = True
23 | cur_dir_not_full_path = os.getcwd().split('/')[-1]
24 | README_START = f'# {cur_dir_not_full_path}\n## What is it?\n'
25 |
26 | # Get config dir from environment or default to ~/.config
27 | CONFIG_DIR = os.getenv('XDG_CONFIG_HOME', os.path.expanduser('~/.config'))
28 | API_KEYS_LOCATION = os.path.join(CONFIG_DIR, 'openaiapirc')
29 |
30 |
31 | def create_template_ini_file():
32 | """
33 | If the ini file does not exist create it and add the organization_id and
34 | secret_key
35 | """
36 | if not os.path.isfile(API_KEYS_LOCATION):
37 | with open(API_KEYS_LOCATION, 'w') as f:
38 | f.write('[openai]\n')
39 | f.write('organization_id=\n')
40 | f.write('secret_key=\n')
41 |
42 | print('OpenAI API config file created at {}'.format(API_KEYS_LOCATION))
43 | print('Please edit it and add your organization ID and secret key')
44 | print('If you do not yet have an organization ID and secret key, you\n'
45 | 'need to register for OpenAI Codex: \n'
46 | 'https://openai.com/blog/openai-codex/')
47 | sys.exit(1)
48 |
49 |
50 | def initialize_openai_api():
51 | """
52 | Initialize the OpenAI API
53 | """
54 | # Check if file at API_KEYS_LOCATION exists
55 | create_template_ini_file()
56 | config = configparser.ConfigParser()
57 | config.read(API_KEYS_LOCATION)
58 |
59 | openai.organization_id = config['openai']['organization_id'].strip('"').strip("'")
60 | openai.api_key = config['openai']['secret_key'].strip('"').strip("'")
61 |
62 |
63 |
64 | def create_input_prompt(length=3000):
65 | input_prompt = ''
66 | files_sorted_by_mod_date = sorted(os.listdir('.'), key=os.path.getmtime)
67 | # Reverse sorted files.
68 | files_sorted_by_mod_date = files_sorted_by_mod_date[::-1]
69 | for filename in files_sorted_by_mod_date:
70 | # Check if file is a image file.
71 | is_image_file = False
72 | for extension in ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.svg']:
73 | if filename.endswith(extension):
74 | is_image_file = True
75 | break
76 | if filename not in FILES_NOT_TO_INCLUDE and not filename.startswith('.') \
77 | and not os.path.isdir(filename) and not is_image_file:
78 | with open(filename) as f:
79 | input_prompt += '\n===================\n# ' + filename + ':\n'
80 | input_prompt += f.read() + '\n'
81 |
82 | input_prompt = input_prompt[:length]
83 | input_prompt += '\n\n===================\n# ' + 'README.md:' + '\n'
84 | input_prompt += README_START
85 |
86 | return input_prompt
87 |
88 |
89 | def generate_completion(input_prompt, num_tokens):
90 | response = openai.Completion.create(engine='code-davinci-001', prompt=input_prompt, temperature=0.5, max_tokens=num_tokens, stream=STREAM, stop='===================\n')
91 | return response
92 |
93 |
94 | def generate_completion_chatgpt(input_prompt, num_tokens):
95 | messages = [
96 | {'role': 'user',
97 | 'content': input_prompt}
98 | ]
99 |
100 | response = openai.ChatCompletion.create(
101 | model="gpt-3.5-turbo",
102 | max_tokens=num_tokens,
103 | messages=messages,
104 | )
105 |
106 | return response
107 |
108 |
109 | def clear_screen_and_display_generated_readme(response):
110 | # Clear screen.
111 | os.system('cls' if os.name == 'nt' else 'clear')
112 | generated_readme = ''
113 | print(README_START)
114 | generated_readme = README_START
115 | while True:
116 | next_response = next(response)
117 | completion = next_response['choices'][0]['text']
118 | # print("completion:", completion)
119 | # print(next(response))
120 | print(completion, end='')
121 | generated_readme = generated_readme + completion
122 | if next_response['choices'][0]['finish_reason'] != None: break
123 |
124 | return generated_readme
125 |
126 |
127 | def clear_screen_and_display_generated_readme_chatgpt(response):
128 | # Clear screen.
129 | os.system('cls' if os.name == 'nt' else 'clear')
130 | generated_readme = ''
131 | print(README_START)
132 |
133 | response_text = response["choices"][0]["message"]['content']
134 | print(response_text)
135 | return response_text
136 |
137 |
138 |
139 |
140 |
141 | def save_readme(readme_text):
142 | '''
143 | Saves the readme.
144 | If a readme already exists ask the user whether he wants
145 | to overwrite it.
146 | '''
147 | if os.path.isfile('README.md'):
148 | answer = input('A README.md already exists. Do you want to overwrite it? [y/N] ')
149 | if answer == '' or answer == 'n' or answer == 'N':
150 | print('\nThe README was not saved.')
151 | return
152 |
153 | with open('README.md', 'w') as f:
154 | f.write(readme_text)
155 |
156 | print('\nREADME.md saved.')
157 |
158 | def generate_until_accepted(input_prompt, num_tokens):
159 | '''
160 | Generate new readmes and ask the user if he wants to save the generated
161 | readme.
162 | '''
163 | while True:
164 | response = generate_completion_chatgpt(input_prompt, num_tokens)
165 | generated_readme = clear_screen_and_display_generated_readme_chatgpt(response)
166 |
167 | # Ask the user if he wants to save the generated readme.
168 | answer = input("\n\nDo you want to save the generated README? [y/N] ")
169 | if answer == '' or answer == 'n' or answer == 'N':
170 | print('\nThe generated README is not saved.')
171 | continue
172 | elif answer == 'y' or answer == 'Y':
173 | save_readme(generated_readme)
174 |
175 | answer = input("\n\nDo you want to generate another README? [Y/n] ")
176 | if answer == '' or answer == 'y' or answer == 'Y':
177 | continue
178 | break
179 |
180 | def get_args():
181 | # Get the number of tokens as positional argument.
182 | parser = argparse.ArgumentParser()
183 | parser.add_argument("--tokens", type=int, default=1024)
184 | args = parser.parse_args()
185 | return args
186 |
187 | if __name__ == '__main__':
188 | args = get_args()
189 | initialize_openai_api()
190 | input_prompt = create_input_prompt()
191 | generate_until_accepted(input_prompt, args.tokens)
192 |
193 |
194 |
195 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | openai
2 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | setup(
4 | name='codex-readme',
5 | scripts=['codex-readme']
6 | )
7 |
--------------------------------------------------------------------------------