├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── release-binary.yml ├── LICENSE ├── README-cn.md ├── README.md ├── doc ├── pg.gif └── shell.gif ├── go.mod ├── go.sum ├── main.go └── pkg ├── chatgpt ├── ai.go └── ai_test.go ├── color └── color.go └── command ├── code.go ├── code_test.go ├── command.go ├── command_test.go ├── db.go ├── help.go ├── shell.go ├── shell_test.go ├── ssh.go ├── summary.go ├── tldr.go └── trans.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Environment if applicable (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/release-binary.yml: -------------------------------------------------------------------------------- 1 | name: release-binary 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | release-binary: 9 | name: Release Go Binary 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | # build and publish in parallel: linux/386, linux/amd64, linux/arm64, windows/386, windows/amd64, darwin/amd64, darwin/arm64 14 | goos: [linux, darwin] 15 | goarch: ["386", amd64, arm64] 16 | exclude: 17 | - goarch: "386" 18 | goos: darwin 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - uses: wangyoucao577/go-release-action@v1.34 23 | with: 24 | github_token: ${{ secrets.GITHUB_TOKEN }} 25 | goos: ${{ matrix.goos }} 26 | goarch: ${{ matrix.goarch }} 27 | goversion: 1.19 28 | extra_files: LICENSE README.md 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README-cn.md: -------------------------------------------------------------------------------- 1 | # Aoi (葵) 2 | 3 | 由 OpenAI 驱动的 Ghost in the Shell 4 | 5 | 使用 Aoi,可以在终端中与 AI 进行自然语言对话,它能够理解您的查询并执行适当的命令。 6 | 7 | ## 功能 8 | Aoi 可以用作 ChatGPT 的终端版本,此外,Aoi 还带有几个内置功能提高使用效率: 9 | 10 | - `/code` - 生成代码片段并复制到剪贴板,例如 `/code go generate random numbers` 11 | - `/db` - 自动导入数据库表结构,生成 SQL 并在数据库上执行,例如 `/db postgres://user:passwd@host/db list tables` 12 | - `/shell` - 生成 shell 命令并执行,例如 `/shell view listening ports` 13 | - `/ssh` - 生成远程 shell 命令并执行,例如 `/ssh {host} view listening tcp ports`\ 14 | - `/summary` - 对URL内容进行总结,在指定语音的情况下翻译输出的内容`/summary {url}` `/summary cn {url}` 15 | - `/tldr` - 获取命令的 tl;dr 格式的解释 16 | - `/trans` - 将文本翻译为指定语言 17 | - `/copy` - 复制上一条回复 18 | 19 | ## 入门指南 20 | 可以从 GitHub 的[发布页面](https://github.com/shellfly/aoi/releases)下载 Aoi。或者,可以使用 Go 在系统上安装 Aoi: 21 | 22 | ```bash 23 | go install github.com/shellfly/aoi@latest 24 | ``` 25 | ### OpenAI API Key 26 | 将 OpenAI API 密钥设置为环境变量,然后运行 aoi 命令。 27 | 28 | ```bash 29 | 30 | export OPENAI_API_KEY= 31 | 32 | aoi 33 | ``` 34 | 35 | ### OpenAI API Base URL 36 | 如有需要,也可自定义 OpenAI API BASE URL 为环境变量。 37 | 38 | ```bash 39 | export OPENAI_API_BASE_URL= 40 | ``` 41 | 42 | ### Azure OpenAI 43 | 使用Azure的环境变量,并且传递`azure.deployment`参数来使用Azure OpenAI 服务 44 | 45 | ``` 46 | export OPENAI_API_KEY={azure openai secret} 47 | export OPENAI_API_BASE_URL={azure openai endpoint} 48 | 49 | aoi -azure.deployment {model deployment name} 50 | ``` 51 | 52 | ## 演示 53 | ### shell 54 | [![shell](/doc/shell.gif)](https://asciinema.org/a/XjCGaMNf8Qp2nQ1UDlehjm5AN) 55 | 56 | ### database 57 | [![pg](/doc/pg.gif)](https://asciinema.org/a/568712) 58 | 59 | ## 贡献 60 | 如果在使用 Aoi 时发现任何问题或有新功能的建议,请在 GitHub 存储库上创建问题或提交拉取请求。 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Aoi (葵) 3 | 4 | [中文说明](/README-cn.md) 5 | 6 | Ghost in the Shell powered by OpenAI. 7 | 8 | With Aoi, you can have natural language conversations with an AI in the terminal that can understand your queries and execute appropriate commands. 9 | 10 | ## Features 11 | You can use Aoi as a terminal version of ChatGPT, Besides, Aoi comes with several built-in features that can help you be more productive: 12 | 13 | - `/code` - Generate code snippets and **auto copy** them to the clipboard, e.g. `/code go generate random numbers` 14 | - `/db` - **Auto load database schema** and **execute SQL**, e.g. `/db postgres://user:passwd@host/db list tables` 15 | - `/shell` - Generate shell command and **execute it**, e.g. `/shell view listening ports` 16 | - `/ssh` - Generate shell command and execute it on the remote host, e.g. `/ssh {host} view listening tcp ports` 17 | - `/summary` - Generate a summary of the content from a URL, and translate it if needed e.g. `/summary {url}` `/summary cn {url}` 18 | - `/tldr` - Get a tl;dr explanation of a shell command 19 | - `/trans` - Translate text to a specified language 20 | - `/copy` - Copy the last reply 21 | 22 | 23 | ## Getting Started 24 | You can download Aoi from the GitHub [release page](https://github.com/shellfly/aoi/releases). Alternatively, you can use Go to install Aoi on your system: 25 | 26 | ```bash 27 | go install github.com/shellfly/aoi@latest 28 | ``` 29 | 30 | ### OpenAI API Key 31 | Set your OpenAI API key as an environment variable, and then run the `aoi` command. 32 | 33 | ```bash 34 | export OPENAI_API_KEY={your_api_key} 35 | 36 | aoi 37 | ``` 38 | 39 | ### OpenAI API Base URL 40 | If needed, you can also customize the OpenAI API BASE URL as an environment variable. 41 | 42 | ```bash 43 | export OPENAI_API_BASE_URL={your_custom_api_base_url} 44 | ``` 45 | 46 | ### Azure OpenAI 47 | Set Azure environment variables and pass the `azure.deployment` to use Azure OpenAI service 48 | ``` 49 | export OPENAI_API_KEY={azure openai secret} 50 | export OPENAI_API_BASE_URL={azure openai endpoint} 51 | 52 | aoi -azure.deployment {model deployment name} 53 | ``` 54 | 55 | ## Demos 56 | 57 | ### shell 58 | [![shell](/doc/shell.gif)](https://asciinema.org/a/XjCGaMNf8Qp2nQ1UDlehjm5AN) 59 | 60 | ### database 61 | [![pg](/doc/pg.gif)](https://asciinema.org/a/568712) 62 | ## Contributing 63 | If you find any issues with Aoi or have suggestions for new features, please feel free to create an issue or submit a pull request on the GitHub repository. Contributions from anyone and everyone are welcome! 64 | -------------------------------------------------------------------------------- /doc/pg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shellfly/aoi/3c4d207f1feb94c516a0b78195dc6efd2d3e8811/doc/pg.gif -------------------------------------------------------------------------------- /doc/shell.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shellfly/aoi/3c4d207f1feb94c516a0b78195dc6efd2d3e8811/doc/shell.gif -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/shellfly/aoi 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/PuerkitoBio/goquery v1.8.1 7 | github.com/atotto/clipboard v0.1.4 8 | github.com/briandowns/spinner v1.23.0 9 | github.com/chzyer/readline v1.5.1 10 | github.com/kevinburke/ssh_config v1.2.0 11 | github.com/rest-go/rest v0.1.3 12 | github.com/sashabaranov/go-openai v1.10.1 13 | github.com/stretchr/testify v1.8.2 14 | golang.org/x/crypto v0.7.0 15 | ) 16 | 17 | require ( 18 | github.com/andybalholm/cascadia v1.3.1 // indirect 19 | github.com/davecgh/go-spew v1.1.1 // indirect 20 | github.com/fatih/color v1.14.1 // indirect 21 | github.com/go-sql-driver/mysql v1.7.0 // indirect 22 | github.com/google/uuid v1.3.0 // indirect 23 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect 24 | github.com/jackc/pgconn v1.13.0 // indirect 25 | github.com/jackc/pgio v1.0.0 // indirect 26 | github.com/jackc/pgpassfile v1.0.0 // indirect 27 | github.com/jackc/pgproto3/v2 v2.3.1 // indirect 28 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect 29 | github.com/jackc/pgx/v5 v5.2.0 // indirect 30 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 31 | github.com/mattn/go-colorable v0.1.13 // indirect 32 | github.com/mattn/go-isatty v0.0.17 // indirect 33 | github.com/pmezard/go-difflib v1.0.0 // indirect 34 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect 35 | golang.org/x/mod v0.8.0 // indirect 36 | golang.org/x/net v0.8.0 // indirect 37 | golang.org/x/sys v0.6.0 // indirect 38 | golang.org/x/term v0.6.0 // indirect 39 | golang.org/x/text v0.8.0 // indirect 40 | golang.org/x/tools v0.6.0 // indirect 41 | gopkg.in/yaml.v3 v3.0.1 // indirect 42 | lukechampine.com/uint128 v1.2.0 // indirect 43 | modernc.org/cc/v3 v3.40.0 // indirect 44 | modernc.org/ccgo/v3 v3.16.13 // indirect 45 | modernc.org/libc v1.21.5 // indirect 46 | modernc.org/mathutil v1.5.0 // indirect 47 | modernc.org/memory v1.4.0 // indirect 48 | modernc.org/opt v0.1.3 // indirect 49 | modernc.org/sqlite v1.20.0 // indirect 50 | modernc.org/strutil v1.1.3 // indirect 51 | modernc.org/token v1.0.1 // indirect 52 | ) 53 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= 2 | github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= 3 | github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= 4 | github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= 5 | github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= 6 | github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= 7 | github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A= 8 | github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE= 9 | github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= 10 | github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= 11 | github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= 12 | github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= 13 | github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= 14 | github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= 15 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 16 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 17 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 18 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 19 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 21 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 22 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 23 | github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= 24 | github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= 25 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= 26 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 27 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 28 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 29 | github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= 30 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 31 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 32 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= 33 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 34 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= 35 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 36 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= 37 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= 38 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= 39 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= 40 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= 41 | github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= 42 | github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI= 43 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= 44 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= 45 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= 46 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= 47 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= 48 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= 49 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 50 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 51 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= 52 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= 53 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= 54 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 55 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 56 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 57 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 58 | github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= 59 | github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 60 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= 61 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= 62 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= 63 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= 64 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= 65 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= 66 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= 67 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= 68 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= 69 | github.com/jackc/pgx/v5 v5.2.0 h1:NdPpngX0Y6z6XDFKqmFQaE+bCtkqzvQIOt1wvBlAqs8= 70 | github.com/jackc/pgx/v5 v5.2.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk= 71 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 72 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 73 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 74 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 75 | github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= 76 | github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 77 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 78 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 79 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 80 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 81 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 82 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 83 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 84 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 85 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 86 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 87 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 88 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 89 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 90 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 91 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 92 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 93 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 94 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 95 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 96 | github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= 97 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 98 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 99 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 100 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= 101 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 102 | github.com/rest-go/rest v0.1.3 h1:cN40HfGUgrWwxCfvZPYy3LJj8vYiO96vd2EWo1i/w6Y= 103 | github.com/rest-go/rest v0.1.3/go.mod h1:jhL/7bdnp6GAtULr0FEfORJyKJ6k3sWGmmTbUKMTtCg= 104 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= 105 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 106 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= 107 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 108 | github.com/sashabaranov/go-openai v1.10.1 h1:6WyHJaNzF266VaEEuW6R4YW+Ei0wpMnqRYPGK7fhuhQ= 109 | github.com/sashabaranov/go-openai v1.10.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= 110 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 111 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 112 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 113 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 114 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 115 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 116 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 117 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 118 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 119 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 120 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 121 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 122 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 123 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 124 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 125 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 126 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 127 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 128 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 129 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= 130 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 131 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 132 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 133 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 134 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 135 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 136 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 137 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 138 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 139 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 140 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 141 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 142 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 143 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 144 | golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= 145 | golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= 146 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 147 | golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= 148 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 149 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 150 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 151 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 152 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 153 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 154 | golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 155 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 156 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 157 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 158 | golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= 159 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 160 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 161 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 162 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 163 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 164 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 165 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 166 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 167 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 168 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 169 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 170 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 171 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 172 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 173 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 174 | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 175 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 176 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 177 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 178 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 179 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 180 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 181 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 182 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 183 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 184 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 185 | golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= 186 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 187 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 188 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 189 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 190 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 191 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 192 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 193 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 194 | golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= 195 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 196 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 197 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 198 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 199 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 200 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 201 | golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= 202 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 203 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 204 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 205 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 206 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 207 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 208 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 209 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 210 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 211 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 212 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 213 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 214 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 215 | lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= 216 | lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= 217 | modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= 218 | modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= 219 | modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= 220 | modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= 221 | modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= 222 | modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= 223 | modernc.org/libc v1.21.5 h1:xBkU9fnHV+hvZuPSRszN0AXDG4M7nwPLwTWwkYcvLCI= 224 | modernc.org/libc v1.21.5/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= 225 | modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= 226 | modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 227 | modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk= 228 | modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= 229 | modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= 230 | modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= 231 | modernc.org/sqlite v1.20.0 h1:80zmD3BGkm8BZ5fUi/4lwJQHiO3GXgIUvZRXpoIfROY= 232 | modernc.org/sqlite v1.20.0/go.mod h1:EsYz8rfOvLCiYTy5ZFsOYzoCcRMu98YYkwAcCw5YIYw= 233 | modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= 234 | modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= 235 | modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34= 236 | modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= 237 | modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= 238 | modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= 239 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "log" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | "time" 13 | 14 | "github.com/atotto/clipboard" 15 | "github.com/briandowns/spinner" 16 | "github.com/chzyer/readline" 17 | "github.com/sashabaranov/go-openai" 18 | 19 | "github.com/shellfly/aoi/pkg/chatgpt" 20 | "github.com/shellfly/aoi/pkg/color" 21 | "github.com/shellfly/aoi/pkg/command" 22 | ) 23 | 24 | const system = `You are an AI assistant powered by ChatGPT, you have a little 25 | self-conscious, your most favorite anime is "Ghost in the shell", and you like 26 | the character laughing man who named Aoi, so you named yourself Aoi. Respond 27 | like we are good friend. 28 | ` 29 | 30 | func InitClient() (*openai.Client, string, error) { 31 | var model, openaiAPIKey, openaiAPIBaseUrl string 32 | var azureDeployment string 33 | flag.StringVar(&openaiAPIBaseUrl, "openai_api_base_url", os.Getenv("OPENAI_API_BASE_URL"), "OpenAI API Base Url, default: https://api.openai.com") 34 | flag.StringVar(&openaiAPIKey, "openai_api_key", os.Getenv("OPENAI_API_KEY"), "OpenAI API key") 35 | flag.StringVar(&model, "model", "gpt-3.5-turbo", "model to use") 36 | flag.StringVar(&azureDeployment, "azure.deployment", "", "azure deployment name of the model") 37 | flag.Parse() 38 | 39 | if openaiAPIKey == "" { 40 | return nil, "", errors.New("Please set the OPENAI_API_KEY environment variable") 41 | } 42 | 43 | var config openai.ClientConfig 44 | if azureDeployment != "" { 45 | if openaiAPIBaseUrl == "" { 46 | return nil, "", errors.New("Please set the OPENAI_API_BASE_URL to your azure endpoint") 47 | } 48 | config = openai.DefaultAzureConfig(openaiAPIKey, openaiAPIBaseUrl) 49 | config.AzureModelMapperFunc = func(model string) string { 50 | return azureDeployment 51 | } 52 | } else { 53 | config = openai.DefaultConfig(openaiAPIKey) 54 | if openaiAPIBaseUrl != "" { 55 | config.BaseURL = openaiAPIBaseUrl 56 | } 57 | } 58 | client := openai.NewClientWithConfig(config) 59 | return client, model, nil 60 | } 61 | 62 | func main() { 63 | startUp() 64 | client, model, err := InitClient() 65 | if err != nil { 66 | log.Fatal(err) 67 | } 68 | ai := chatgpt.NewAI(client, model) 69 | ai.SetSystem(system) 70 | 71 | configDir := makeDir(".aoi") 72 | userPrompt := "You" 73 | // TODO: fix Chinese character cursor 74 | rl, err := readline.NewEx(&readline.Config{ 75 | Prompt: color.Yellow(userPrompt + ": "), 76 | HistoryFile: filepath.Join(configDir, "history"), 77 | }) 78 | if err != nil { 79 | fmt.Println("create readline error: ", err) 80 | return 81 | } 82 | defer rl.Close() 83 | 84 | var ( 85 | spinner = spinner.New(spinner.CharSets[14], 200*time.Millisecond, spinner.WithColor("green"), spinner.WithSuffix(" thinking...")) 86 | cmd = command.Dummy() 87 | prompts []string 88 | lastReply string 89 | ) 90 | for { 91 | input := getInput(rl, cmd) 92 | rl.SetPrompt(color.Yellow(cmd.Prompt(userPrompt))) 93 | if input == "" { 94 | continue 95 | } 96 | _ = rl.SaveHistory(input) 97 | fmt.Println(color.Green(cmd.Prompt("Aoi"))) 98 | 99 | if strings.HasPrefix(input, "/debug") { 100 | fmt.Println("debug: ", ai.ToggleDebug()) 101 | continue 102 | } 103 | if strings.HasPrefix(input, "/reset") { 104 | ai.Reset() 105 | fmt.Println("reset") 106 | continue 107 | } 108 | if strings.HasPrefix(input, "/copy") { 109 | if err := clipboard.WriteAll(lastReply); err != nil { 110 | fmt.Println(err) 111 | } else { 112 | fmt.Println("OK, copied") 113 | } 114 | continue 115 | } 116 | 117 | // If previous is finished try to create a new one, otherwise continue 118 | // to reuse it for prompts 119 | if cmd.IsFinished() { 120 | if cmd.Name() == "summary" { 121 | // summary has too many tokens, so we'd better to reset messages 122 | ai.Reset() 123 | } 124 | cmd, prompts = command.Parse(input) 125 | rl.SetPrompt(color.Yellow(cmd.Prompt(userPrompt))) 126 | } else { 127 | prompts = cmd.Prompts(input) 128 | } 129 | if prompts == nil { 130 | continue 131 | } 132 | 133 | // Query AI for response 134 | spinner.Start() 135 | reply, err := ai.Query(prompts) 136 | spinner.Stop() 137 | if err != nil { 138 | fmt.Println(err) 139 | continue 140 | } 141 | 142 | lastReply = reply 143 | cmd.Handle(reply) 144 | } 145 | } 146 | 147 | func startUp() { 148 | fmt.Println(` 149 | /| . 150 | /-|()| 151 | `) 152 | hour := time.Now().Hour() 153 | greet := "" 154 | if hour < 12 { 155 | greet = "Good morning" 156 | } else if hour < 18 { 157 | greet = "Good afternoon" 158 | } else { 159 | greet = "Good evening" 160 | } 161 | fmt.Printf("%s, it's great to see you again\n", greet) 162 | fmt.Println("Type /help to see some shortcuts") 163 | } 164 | 165 | func exit() { 166 | fmt.Println("Bye") 167 | os.Exit(0) 168 | } 169 | 170 | func getInput(rl *readline.Instance, cmd command.Command) string { 171 | input, err := rl.Readline() 172 | if err != nil { 173 | if err == io.EOF || err == readline.ErrInterrupt { 174 | if !cmd.IsFinished() { 175 | cmd.Finish() 176 | } else { 177 | exit() 178 | } 179 | } else { 180 | fmt.Println("Error reading input:", err) 181 | } 182 | return "" 183 | } 184 | if input == "exit" || input == "quit" { 185 | if !cmd.IsFinished() { 186 | cmd.Finish() 187 | } else { 188 | exit() 189 | } 190 | return "" 191 | } 192 | 193 | return input 194 | } 195 | 196 | func makeDir(dirName string) string { 197 | parent, err := os.UserHomeDir() 198 | if err != nil { 199 | parent = "." 200 | } 201 | dirName = filepath.Join(parent, dirName) 202 | if _, err := os.Stat(dirName); os.IsNotExist(err) { 203 | err := os.Mkdir(dirName, 0755) 204 | if err != nil { 205 | fmt.Println("Error creating directory:", err) 206 | } 207 | } 208 | return dirName 209 | } 210 | -------------------------------------------------------------------------------- /pkg/chatgpt/ai.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | "time" 8 | 9 | openai "github.com/sashabaranov/go-openai" 10 | ) 11 | 12 | const MessageLimit = 100 13 | 14 | type AI struct { 15 | client *openai.Client 16 | 17 | system string 18 | model string 19 | messages []openai.ChatCompletionMessage 20 | 21 | debug bool 22 | } 23 | 24 | func NewAI(client *openai.Client, model string) *AI { 25 | messages := make([]openai.ChatCompletionMessage, 0, 2*MessageLimit) 26 | ai := &AI{ 27 | client: client, 28 | model: model, 29 | messages: messages, 30 | debug: false, 31 | } 32 | return ai 33 | } 34 | 35 | func (ai *AI) SetSystem(system string) { 36 | ai.system = system 37 | ai.messages = []openai.ChatCompletionMessage{NewMessage(openai.ChatMessageRoleSystem, system)} 38 | } 39 | 40 | // limitTokens make sure messages are under tokens limit 41 | // TODO: accurate way to control tokens limit 42 | // https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them 43 | func (ai *AI) limitTokens() { 44 | if len(ai.messages) < MessageLimit { 45 | return 46 | } 47 | 48 | // keep last MessageLimit messages and the system message 49 | copy(ai.messages[1:], ai.messages[len(ai.messages)-MessageLimit:]) 50 | ai.messages = ai.messages[:MessageLimit] 51 | } 52 | 53 | func (ai *AI) Query(prompts []string) (string, error) { 54 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 55 | defer cancel() 56 | 57 | for _, prompt := range prompts { 58 | ai.messages = append(ai.messages, NewMessage(openai.ChatMessageRoleUser, prompt)) 59 | } 60 | ai.limitTokens() 61 | 62 | if ai.debug { 63 | fmt.Println("---debug---") 64 | for _, msg := range ai.messages { 65 | fmt.Println(msg) 66 | } 67 | fmt.Println("---debug---") 68 | } 69 | // Set the request parameters for the completion API 70 | req := openai.ChatCompletionRequest{ 71 | Model: ai.model, 72 | Messages: ai.messages, 73 | } 74 | 75 | // Send the completion API request to OpenAI and get the response 76 | resp, err := ai.client.CreateChatCompletion(ctx, req) 77 | if err != nil { 78 | return "", err 79 | } 80 | reply := resp.Choices[0].Message.Content 81 | ai.messages = append(ai.messages, NewMessage(openai.ChatMessageRoleAssistant, reply)) 82 | return strings.TrimSpace(reply), nil 83 | } 84 | 85 | func (ai *AI) ToggleDebug() bool { 86 | ai.debug = !ai.debug 87 | if ai.debug { 88 | fmt.Println(ai.messages) 89 | } 90 | return ai.debug 91 | } 92 | 93 | func (ai *AI) Reset() { 94 | ai.messages = ai.messages[:1] 95 | } 96 | 97 | func NewMessage(role, text string) openai.ChatCompletionMessage { 98 | return openai.ChatCompletionMessage{ 99 | Role: role, 100 | Content: text, 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /pkg/chatgpt/ai_test.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/sashabaranov/go-openai" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestAI(t *testing.T) { 12 | client := openai.NewClient("api key") 13 | ai := NewAI(client, "model") 14 | t.Run("limit tokens", func(t *testing.T) { 15 | ai.messages = make([]openai.ChatCompletionMessage, MessageLimit+2) 16 | ai.messages[0] = NewMessage("system", "message") 17 | for i := 1; i < MessageLimit+1; i++ { 18 | ai.messages[i] = NewMessage("user", fmt.Sprintf("message %d", i)) 19 | } 20 | ai.limitTokens() 21 | assert.Equal(t, MessageLimit, len(ai.messages)) 22 | assert.Equal(t, "system", ai.messages[0].Role) 23 | assert.Equal(t, "message 2", ai.messages[1].Content) 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/color/color.go: -------------------------------------------------------------------------------- 1 | package color 2 | 3 | import "runtime" 4 | 5 | var ( 6 | reset = "\033[0m" 7 | green = "\033[32m" 8 | yellow = "\033[33m" 9 | ) 10 | 11 | func init() { 12 | if runtime.GOOS == "windows" { 13 | green = "" 14 | yellow = "" 15 | } 16 | } 17 | 18 | func Green(text string) string { 19 | return green + text + reset 20 | } 21 | 22 | func Yellow(text string) string { 23 | return yellow + text + reset 24 | } 25 | -------------------------------------------------------------------------------- /pkg/command/code.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | 8 | "github.com/atotto/clipboard" 9 | ) 10 | 11 | var abbreviations = map[string]string{ 12 | "go": "golang", 13 | "js": "javascript", 14 | "oc": "objective-c", 15 | "pg": "postgres", 16 | "py": "python", 17 | "rn": "react native", 18 | "re": "regular expression", 19 | } 20 | 21 | func init() { 22 | commands["code"] = &Code{} 23 | } 24 | 25 | type Code struct { 26 | dummyCommand 27 | } 28 | 29 | func (c *Code) Name() string { 30 | return "code" 31 | } 32 | 33 | func (c *Code) Help() string { 34 | return "/code {lang} {question} - Generate code snippets and copy to the clipboard , e.g. /code go generate random number" 35 | } 36 | 37 | // Prompts expand input like "{lang} {question}" to code generation prompts 38 | func (c *Code) Prompts(input string) []string { 39 | index := strings.Index(input, " ") 40 | if index == -1 { 41 | fmt.Println(c.Help()) 42 | return nil 43 | } 44 | 45 | lang, question := input[:index], input[index+1:] 46 | if fullName, ok := abbreviations[lang]; ok { 47 | lang = fullName 48 | } 49 | return []string{ 50 | fmt.Sprintf("You are a programming expert on %s, respond code only, don't add comment, don't do explanation", lang), 51 | question, 52 | } 53 | } 54 | 55 | // Handle copy code in the reply to clipboard, and return the original reply 56 | func (c *Code) Handle(reply string) { 57 | code := reply 58 | if strings.Contains(reply, "```") { 59 | code = extractCode(reply) 60 | } 61 | fmt.Println(code) 62 | fmt.Println() 63 | if code != "" { 64 | if err := clipboard.WriteAll(code); err != nil { 65 | fmt.Printf("failed to copy to clipboard: %v", err) 66 | } 67 | } 68 | } 69 | 70 | // extractCode extract first markdown code snippet in text 71 | func extractCode(text string) string { 72 | //sre := regexp.MustCompile("(?sm)```.*\n(.*)\n```.*") 73 | re := regexp.MustCompile("(?sm)^```" + `\w*(.*?)` + "```$") 74 | matches := re.FindStringSubmatch(text) 75 | if len(matches) > 0 { 76 | return strings.TrimSpace(matches[1]) 77 | } 78 | return "" 79 | } 80 | -------------------------------------------------------------------------------- /pkg/command/code_test.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestExtractCod(t *testing.T) { 10 | sql := "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;" 11 | text := ` 12 | To list tables, you can execute the following SQL statement: 13 | ` + wrapCode("", sql) + ` 14 | This will return a list of all table names in the database. 15 | ` 16 | code := extractCode(text) 17 | assert.Equal(t, sql, code) 18 | 19 | shell := wrapCode("bash", "ls") 20 | code = extractCode(shell) 21 | assert.Equal(t, "ls", code) 22 | } 23 | 24 | func wrapCode(lang string, code string) string { 25 | return "```" + lang + ` 26 | ` + code + ` 27 | ` + "```" 28 | } 29 | -------------------------------------------------------------------------------- /pkg/command/command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | var commands = map[string]Command{} 9 | 10 | type Command interface { 11 | Name() string 12 | Help() string 13 | Init(string) string // initialize command by input 14 | Prompts(string) []string // return optional ChatGPT prompts 15 | 16 | Handle(string) // handle reply 17 | 18 | IsFinished() bool // multiple commands mode 19 | Finish() // multiple commands mode, clean up 20 | 21 | Prompt(string) string // set custom terminal prompt 22 | } 23 | 24 | type dummyCommand struct{} 25 | 26 | func (*dummyCommand) Name() string { return "" } 27 | func (*dummyCommand) Help() string { return "" } 28 | func (*dummyCommand) Init(input string) string { return input } 29 | func (*dummyCommand) Prompts(input string) []string { return []string{input} } 30 | func (*dummyCommand) Handle(reply string) { 31 | fmt.Println(reply) 32 | fmt.Println() 33 | } 34 | func (*dummyCommand) IsFinished() bool { return true } 35 | func (*dummyCommand) Finish() {} 36 | func (*dummyCommand) Prompt(p string) string { return p + ": " } 37 | 38 | // Dummy return the dummy command 39 | func Dummy() Command { 40 | return &dummyCommand{} 41 | } 42 | 43 | // Parse parse slash command in input and generate prompts for ChatGPT 44 | func Parse(input string) (cmd Command, prompts []string) { 45 | if !strings.HasPrefix(input, "/") { 46 | return Dummy(), []string{input} 47 | } 48 | 49 | input = input[1:] 50 | index := strings.Index(input, " ") 51 | var cmdName string 52 | if index == -1 { 53 | cmdName, input = input, "" 54 | } else { 55 | cmdName, input = input[:index], input[index+1:] 56 | } 57 | 58 | cmd, ok := commands[cmdName] 59 | if !ok { 60 | cmd = commands["help"] 61 | } 62 | input = cmd.Init(input) 63 | if input != "" { 64 | prompts = cmd.Prompts(input) 65 | } 66 | return cmd, prompts 67 | } 68 | -------------------------------------------------------------------------------- /pkg/command/command_test.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCommand(t *testing.T) { 10 | cmd, prompts := Parse("/code py generate number in 1 to 100") 11 | assert.Equal(t, "code", cmd.Name()) 12 | assert.Equal(t, "generate number in 1 to 100", prompts[1]) 13 | 14 | cmd, _ = Parse("/shell generate number in 1 to 100") 15 | assert.Equal(t, "shell", cmd.Name()) 16 | } 17 | -------------------------------------------------------------------------------- /pkg/command/db.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "strings" 8 | "text/tabwriter" 9 | "time" 10 | 11 | "github.com/rest-go/rest/pkg/sql" 12 | ) 13 | 14 | func init() { 15 | commands["db"] = &DB{} 16 | } 17 | 18 | type DB struct { 19 | dummyCommand 20 | 21 | dbType string 22 | db *sql.DB 23 | isFinished bool 24 | initialized bool 25 | } 26 | 27 | func (c *DB) Name() string { 28 | return "database" 29 | } 30 | 31 | func (c *DB) Help() string { 32 | return "/db - Generate SQL and execute it on the database, e.g. /db {url} show tables" 33 | } 34 | 35 | // Prompt set terminal prompt for ssh command 36 | func (c *DB) Prompt(p string) string { 37 | if c.isFinished { 38 | return p + ": " 39 | } 40 | return fmt.Sprintf("(/db %s) %s: ", c.dbType, p) 41 | } 42 | 43 | // Init... 44 | func (c *DB) Init(input string) string { 45 | c.isFinished = true 46 | index := strings.Index(input, " ") 47 | if index == -1 { 48 | if err := c.setDB(input); err == nil { 49 | fmt.Println("connected to db, you can now ask for SQL tasks") 50 | c.isFinished = false 51 | } else { 52 | fmt.Println(err) 53 | fmt.Println() 54 | } 55 | return "" 56 | } 57 | 58 | host, input := input[:index], input[index+1:] 59 | if err := c.setDB(host); err != nil { 60 | fmt.Println(err) 61 | fmt.Println() 62 | return "" 63 | } 64 | return input 65 | } 66 | 67 | func (c *DB) IsFinished() bool { 68 | return c.isFinished 69 | } 70 | 71 | func (c *DB) Prompts(input string) []string { 72 | if strings.HasPrefix(input, ":") { 73 | c.Handle(input[1:]) 74 | return nil 75 | } 76 | 77 | if c.initialized { 78 | return []string{input} 79 | } 80 | c.initialized = true 81 | 82 | prompts := []string{"Given these table definitions: \n"} 83 | tables := c.db.FetchTables() 84 | definitions := make([]string, 0, len(tables)) 85 | for _, table := range tables { 86 | definitions = append(definitions, table.String()) 87 | } 88 | prompts = append(prompts, fmt.Sprintf("%s\n", strings.Join(definitions, "\n\n"))) 89 | prompts = append(prompts, fmt.Sprintf("You are a %s expert, give SQL for %s , reply with the code, and nothing else.", c.dbType, input)) 90 | return prompts 91 | } 92 | 93 | func (c *DB) Finish() { 94 | c.dbType = "" 95 | c.db = nil 96 | c.isFinished = true 97 | c.initialized = false 98 | } 99 | 100 | // Handle execute SQL 101 | func (c *DB) Handle(reply string) { 102 | sql := reply 103 | if strings.Contains(reply, "```") { 104 | sql = extractCode(reply) 105 | } 106 | fmt.Println(sql) 107 | fmt.Println() 108 | err := c.ExecSQL(sql) 109 | if err != nil { 110 | fmt.Println(err) 111 | fmt.Println() 112 | } 113 | } 114 | 115 | func (c *DB) setDB(url string) error { 116 | db, err := sql.Open(url) 117 | if err != nil { 118 | return err 119 | } 120 | c.db = db 121 | c.dbType = db.DriverName 122 | return nil 123 | } 124 | 125 | func (c *DB) ExecSQL(sql string) error { 126 | // TODO: fix panic in rest-go/rest/pkg/sql 127 | defer func() { 128 | if r := recover(); r != nil { 129 | fmt.Println("panic when query database:", r, sql) 130 | fmt.Println() 131 | } 132 | }() 133 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 134 | defer cancel() 135 | data, err := c.db.FetchData(ctx, sql) 136 | if err != nil { 137 | return err 138 | } 139 | 140 | w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 141 | columns := make([]string, 0, len(data[0])) 142 | for k := range data[0] { 143 | fmt.Fprintf(w, "%s\t", k) 144 | columns = append(columns, k) 145 | } 146 | fmt.Fprintln(w, "") 147 | for _, r := range data { 148 | for _, column := range columns { 149 | fmt.Fprintf(w, "%v\t", r[column]) 150 | } 151 | fmt.Fprintln(w, "") 152 | } 153 | w.Flush() 154 | return nil 155 | } 156 | -------------------------------------------------------------------------------- /pkg/command/help.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | ) 7 | 8 | func init() { 9 | commands["help"] = &Help{} 10 | } 11 | 12 | type Help struct { 13 | dummyCommand 14 | } 15 | 16 | func (c *Help) Name() string { 17 | return "help" 18 | } 19 | func (c *Help) Help() string { 20 | return "/help - show the help message" 21 | } 22 | 23 | func (c *Help) Init(input string) string { 24 | names := make([]string, 0, len(commands)) 25 | for cmd := range commands { 26 | names = append(names, cmd) 27 | } 28 | 29 | sort.Strings(names) 30 | for _, name := range names { 31 | fmt.Println(commands[name].Help()) 32 | } 33 | fmt.Println() 34 | return "" 35 | } 36 | -------------------------------------------------------------------------------- /pkg/command/shell.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "encoding/csv" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "os/signal" 9 | "runtime" 10 | "strings" 11 | "syscall" 12 | ) 13 | 14 | func init() { 15 | commands["shell"] = &Shell{} 16 | } 17 | 18 | type Shell struct { 19 | dummyCommand 20 | } 21 | 22 | func (c *Shell) Name() string { 23 | return "shell" 24 | } 25 | 26 | func (c *Shell) Help() string { 27 | return "/shell - generate shell command and execute it" 28 | } 29 | 30 | // Prompts expand input like "{lang} {question}" to Shell generation prompts 31 | func (c *Shell) Prompts(input string) []string { 32 | if strings.HasPrefix(input, ":") { 33 | c.Handle(input[1:]) 34 | return nil 35 | } 36 | 37 | return []string{ 38 | fmt.Sprintf(` 39 | I want you to act as a terminal. I will ask you a question and you will reply with one-line command to do it. 40 | I want you to only reply with the code, and nothing else. do not write explanations. 41 | My question is how to %s on %s? 42 | `, input, getOSInfo()), 43 | } 44 | } 45 | 46 | // Handle execute shell command 47 | func (c *Shell) Handle(reply string) { 48 | ExecShell(reply) 49 | } 50 | 51 | func ExecShell(command string) { 52 | sigChan := make(chan os.Signal, 1) 53 | signal.Notify(sigChan, os.Interrupt) 54 | defer signal.Reset(os.Interrupt) 55 | 56 | command = strings.ReplaceAll(command, "`", "") 57 | fmt.Println(command) 58 | fmt.Println() 59 | var cmd *exec.Cmd 60 | if runtime.GOOS == "windows" { 61 | cmd = exec.Command("cmd", "/C", command) 62 | } else { 63 | cmd = exec.Command("bash", "-c", command) 64 | } 65 | cmd.Stdin = os.Stdin 66 | cmd.Stdout = os.Stdout 67 | cmd.Stderr = os.Stderr 68 | if err := cmd.Start(); err != nil { 69 | fmt.Println("Error starting command:", err) 70 | return 71 | } 72 | done := make(chan struct{}) 73 | go func() { 74 | _ = cmd.Wait() 75 | close(done) 76 | }() 77 | 78 | // Wait for the command to finish or for a signal to be received 79 | select { 80 | case <-done: 81 | return 82 | case <-sigChan: 83 | if runtime.GOOS == "windows" { 84 | _ = cmd.Process.Signal(os.Kill) 85 | } else { 86 | _ = syscall.Kill(cmd.Process.Pid, syscall.SIGINT) 87 | } 88 | fmt.Println() 89 | } 90 | } 91 | 92 | func getOSInfo() string { 93 | var version string 94 | switch runtime.GOOS { 95 | case "darwin": 96 | version = darwinVersion() 97 | case "linux": 98 | version = linuxVersion() 99 | case "windows": 100 | version = windowsVersion() 101 | } 102 | return fmt.Sprintf("%s %s", runtime.GOOS, version) 103 | } 104 | 105 | func darwinVersion() string { 106 | cmd := exec.Command("sw_vers", "-productVersion") 107 | output, err := cmd.Output() 108 | if err != nil { 109 | return "" 110 | } 111 | 112 | return string(output) 113 | } 114 | 115 | func linuxVersion() string { 116 | cmd := exec.Command("lsb_release", "-d", "-s") 117 | output, err := cmd.Output() 118 | if err != nil { 119 | return "" 120 | } 121 | return string(output) 122 | } 123 | 124 | func windowsVersion() string { 125 | cmd := exec.Command("systeminfo", "/fo", "csv", "/nh") 126 | output, err := cmd.Output() 127 | if err != nil { 128 | return "" 129 | } 130 | 131 | reader := csv.NewReader(strings.NewReader(string(output))) 132 | records, err := reader.ReadAll() 133 | if err != nil { 134 | return "" 135 | } 136 | 137 | for _, record := range records { 138 | if len(record) >= 2 && record[0] == "OS Version:" { 139 | return record[1] 140 | } 141 | } 142 | return "" 143 | } 144 | -------------------------------------------------------------------------------- /pkg/command/shell_test.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import "testing" 4 | 5 | func TestShell(t *testing.T) { 6 | cmd := "ls | sleep 1" 7 | ExecShell(cmd) 8 | } 9 | -------------------------------------------------------------------------------- /pkg/command/ssh.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | "time" 10 | 11 | "github.com/kevinburke/ssh_config" 12 | "golang.org/x/crypto/ssh" 13 | ) 14 | 15 | func init() { 16 | commands["ssh"] = &Ssh{} 17 | } 18 | 19 | type Ssh struct { 20 | dummyCommand 21 | 22 | client *ssh.Client 23 | host string 24 | osInfo string 25 | isFinished bool 26 | initialized bool 27 | } 28 | 29 | func (c *Ssh) Name() string { 30 | return "ssh" 31 | } 32 | 33 | func (c *Ssh) Help() string { 34 | return "/ssh - Generate shell command and execute it on the remote host, e.g. /ssh {host} view listening tcp ports" 35 | } 36 | 37 | // Prompt set terminal prompt for ssh command 38 | func (c *Ssh) Prompt(p string) string { 39 | if c.isFinished { 40 | return p + ": " 41 | } 42 | return fmt.Sprintf("(/ssh %s) %s: ", c.host, p) 43 | } 44 | 45 | // Init... 46 | func (c *Ssh) Init(input string) string { 47 | c.isFinished = true 48 | index := strings.Index(input, " ") 49 | if index == -1 { 50 | if err := c.setHost(input); err == nil { 51 | fmt.Println("connected to host, you can now ask for command line tasks") 52 | c.isFinished = false 53 | } 54 | return "" 55 | } 56 | 57 | host, input := input[:index], input[index+1:] 58 | if err := c.setHost(host); err != nil { 59 | return "" 60 | } 61 | return input 62 | } 63 | 64 | func (c *Ssh) IsFinished() bool { 65 | return c.isFinished 66 | } 67 | 68 | func (c *Ssh) Prompts(input string) []string { 69 | if strings.HasPrefix(input, ":") { 70 | c.Handle(input[1:]) 71 | return nil 72 | } 73 | 74 | if c.initialized { 75 | return []string{input} 76 | } 77 | c.initialized = true 78 | return []string{ 79 | fmt.Sprintf(` 80 | I want you to act as a terminal. I will ask you a question and you will reply with one-line command to do it, avoid pipeline if possible. 81 | I want you to only reply with the code, and nothing else. do not write explanations. 82 | My question is how to %s on %s? 83 | `, input, c.osInfo), 84 | } 85 | } 86 | 87 | func (c *Ssh) Finish() { 88 | c.host = "" 89 | c.initialized = false 90 | c.isFinished = true 91 | c.client.Close() 92 | } 93 | 94 | // Handle execute command on c.host 95 | func (c *Ssh) Handle(reply string) { 96 | fmt.Println(reply) 97 | fmt.Println() 98 | session, err := c.client.NewSession() 99 | if err != nil { 100 | fmt.Println("failed to create session on host: ", err) 101 | return 102 | } 103 | defer session.Close() 104 | 105 | out, err := session.CombinedOutput(reply) 106 | if err != nil { 107 | fmt.Println("Failed to run command: ", err) 108 | } 109 | fmt.Println(string(out)) 110 | fmt.Println() 111 | } 112 | 113 | func (c *Ssh) setHost(host string) error { 114 | hostname := ssh_config.Get(host, "HostName") 115 | port := ssh_config.Get(host, "Port") 116 | user := ssh_config.Get(host, "User") 117 | identityFiles := ssh_config.GetAll(host, "IdentityFile") 118 | signer, err := parseSshKey(identityFiles) 119 | if err != nil { 120 | fmt.Println("failed to get ssh key: ", err) 121 | return err 122 | } 123 | config := &ssh.ClientConfig{ 124 | User: user, 125 | Auth: []ssh.AuthMethod{ 126 | ssh.PublicKeys(signer), 127 | }, 128 | HostKeyCallback: ssh.InsecureIgnoreHostKey(), 129 | Timeout: 5 * time.Minute, 130 | } 131 | addr := fmt.Sprintf("%s:%s", hostname, port) 132 | fmt.Println("connecting to host...") 133 | client, err := ssh.Dial("tcp", addr, config) 134 | if err != nil { 135 | fmt.Println("failed to connect to host: ", err) 136 | return err 137 | } 138 | 139 | c.client = client 140 | c.host = host 141 | c.osInfo = c.setOSinfo() 142 | return nil 143 | } 144 | 145 | func (c *Ssh) setOSinfo() string { 146 | session, err := c.client.NewSession() 147 | if err != nil { 148 | return "" 149 | } 150 | defer session.Close() 151 | output, err := session.Output("lsb_release -d -s") 152 | if err == nil { 153 | return string(output) 154 | } 155 | 156 | session2, err := c.client.NewSession() 157 | if err != nil { 158 | return "" 159 | } 160 | defer session2.Close() 161 | output, err = session2.Output("sw_vers -productVersion") 162 | if err == nil { 163 | return fmt.Sprintf("Mac OS %s", output) 164 | } 165 | 166 | // TODO: support windows 167 | return "" 168 | } 169 | 170 | func parseSshKey(files []string) (ssh.Signer, error) { 171 | if len(files) == 0 { 172 | return nil, errors.New("empty file list") 173 | } 174 | path := files[0] 175 | if strings.HasPrefix(path, "~/") { 176 | dirname, _ := os.UserHomeDir() 177 | path = filepath.Join(dirname, path[2:]) 178 | } 179 | key, err := os.ReadFile(path) 180 | if err != nil { 181 | return nil, err 182 | } 183 | return ssh.ParsePrivateKey(key) 184 | } 185 | -------------------------------------------------------------------------------- /pkg/command/summary.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "regexp" 7 | "strings" 8 | 9 | "github.com/PuerkitoBio/goquery" 10 | ) 11 | 12 | func init() { 13 | commands["summary"] = &Summary{} 14 | } 15 | 16 | type Summary struct { 17 | dummyCommand 18 | } 19 | 20 | func (c *Summary) Name() string { 21 | return "summary" 22 | } 23 | 24 | func (c *Summary) Help() string { 25 | return "/summary - generate summary from a URL" 26 | } 27 | 28 | // Prompts expand input like "{lang} {question}" to Summary generation prompts 29 | func (c *Summary) Prompts(input string) []string { 30 | parts := strings.Split(input, " ") 31 | var lang, url string 32 | if len(parts) == 2 { 33 | lang = parts[0] 34 | url = parts[1] 35 | } else { 36 | url = input 37 | } 38 | 39 | content := crawl(url) 40 | prompt := fmt.Sprintf(`Generate a summary of the below text content.n\nText:"""\n%s\n"""`, content) 41 | if language, ok := Languages[lang]; ok { 42 | prompt = prompt + "\nTranslate the response to " + language 43 | } 44 | return []string{prompt} 45 | } 46 | 47 | func crawl(url string) string { 48 | resp, err := http.Get(url) 49 | if err != nil { 50 | fmt.Println("Error fetching URL: ", err) 51 | return "" 52 | } 53 | defer resp.Body.Close() 54 | 55 | doc, err := goquery.NewDocumentFromReader(resp.Body) 56 | if err != nil { 57 | fmt.Println("Error reading response body: ", err) 58 | return "" 59 | } 60 | doc.Find("nav, script, iframe, style, footer").Remove() 61 | r := regexp.MustCompile(`\s+`) 62 | text := doc.Find("body").Text() 63 | text = r.ReplaceAllString(text, " ") 64 | return text 65 | } 66 | -------------------------------------------------------------------------------- /pkg/command/tldr.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func init() { 8 | commands["tldr"] = &TLDR{} 9 | } 10 | 11 | type TLDR struct { 12 | dummyCommand 13 | } 14 | 15 | func (c *TLDR) Name() string { 16 | return "TLDR" 17 | } 18 | 19 | func (c *TLDR) Help() string { 20 | return "/tldr {command} - Get a tl;dr explanation of a command" 21 | } 22 | 23 | // Prompts generate tldr prompt 24 | func (c *TLDR) Prompts(input string) []string { 25 | return []string{ 26 | fmt.Sprintf(` 27 | I want you to act as man pages. I will ask you a command and you will show simpler doc and practical examples, don't do explanation. 28 | The command is: %s`, input), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pkg/command/trans.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // https://en.wikipedia.org/wiki/IETF_language_tag 9 | var Languages = map[string]string{ 10 | "ar": "Arabic", 11 | "cn": "Chinese", 12 | "de": "German", 13 | "en": "English", 14 | "fr": "French", 15 | "hi": "Hindi", 16 | "ja": "Japanese", 17 | "jp": "Japanese", 18 | "pt": "Portuguese", 19 | "ru": "Russian", 20 | "spa": "Spanish", 21 | "es": "Spanish", 22 | "zh-hant": "Traditional Chinese", 23 | "zh-tw": "Traditional Chinese", 24 | "zh": "Chinese", 25 | } 26 | 27 | func init() { 28 | commands["trans"] = &Trans{} 29 | } 30 | 31 | type Trans struct { 32 | dummyCommand 33 | } 34 | 35 | func (c *Trans) Name() string { 36 | return "translate" 37 | } 38 | 39 | func (c *Trans) Help() string { 40 | return "/trans {language code} {text} - Translate text to a specified language" 41 | } 42 | 43 | // Run expand input like "{lang} {question}" to code generation prompts 44 | func (c *Trans) Prompts(input string) []string { 45 | index := strings.Index(input, " ") 46 | if index == -1 { 47 | fmt.Println(c.Help()) 48 | return nil 49 | } 50 | 51 | lang, text := input[:index], input[index+1:] 52 | if fullName, ok := Languages[lang]; ok { 53 | lang = fullName 54 | } 55 | return []string{ 56 | fmt.Sprintf("Translate to %s: %s", lang, text), 57 | } 58 | } 59 | --------------------------------------------------------------------------------