├── bun.lockb ├── doc └── img │ ├── demo.gif │ └── logo.svg ├── package.json ├── lib └── manai-preview-format.zsh ├── tsconfig.json ├── LICENSE ├── manai.zsh ├── README.md ├── download-manai.zsh ├── .gitignore ├── .github └── workflows │ └── build-and-release.yaml └── app.ts /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mecab/manai/HEAD/bun.lockb -------------------------------------------------------------------------------- /doc/img/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mecab/manai/HEAD/doc/img/demo.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "manai", 3 | "module": "app.ts", 4 | "bin": "app.ts", 5 | "type": "module", 6 | "devDependencies": { 7 | "@types/bun": "latest", 8 | "@types/diff": "^5.2.1" 9 | }, 10 | "peerDependencies": { 11 | "typescript": "^5.0.0" 12 | }, 13 | "dependencies": { 14 | "diff": "^5.2.0", 15 | "openai": "^4.47.1" 16 | }, 17 | "scripts": { 18 | "build": "bun build ./app.ts --compile --outfile bin/manai --target bun" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/manai-preview-format.zsh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | # TODO: Better markers for diff e.g. invisible unicode characters 4 | ADD_START='追追追'; 5 | DEL_START='削削削'; 6 | DIFF_END='終終終'; 7 | 8 | description="$1" 9 | diff_markup="$2" 10 | 11 | diff_colored=$(echo -E "$diff_markup" | sed "s/$ADD_START/\x1b[32m/g; s/$DEL_START/\x1b[31m/g; s/$DIFF_END/\x1b[0m/g"); 12 | 13 | local BLUE='\e[34m' 14 | local NC='\e[0m' 15 | 16 | printf "${BLUE}%s${NC}\n\n%b${NC}\n" "$description" "$diff_colored" 17 | 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Enable latest features 4 | "lib": ["ESNext", "DOM"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noFallthroughCasesInSwitch": true, 21 | 22 | // Some stricter flags (disabled by default) 23 | "noUnusedLocals": false, 24 | "noUnusedParameters": false, 25 | "noPropertyAccessFromIndexSignature": false, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 mecab 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 | -------------------------------------------------------------------------------- /manai.zsh: -------------------------------------------------------------------------------- 1 | __manai_base="$(dirname "${BASH_SOURCE:-$0}")" 2 | 3 | function __manai_format() { 4 | local WHITE='\033[37m' 5 | local NC='\033[0m' 6 | 7 | while read line; do 8 | IFS=$'\t' read -r -A columns <<< "$line" 9 | printf "${WHITE}%s${NC}\t%s\t%s\t%b\n" "${columns[1]}" "${columns[2]}" "${columns[3]}" "${columns[4]}" 10 | done 11 | } 12 | 13 | function __manai_preview_format() { 14 | echo "$1" | sed "s/あ/\x1b[32m/g; s/い/\x1b[31m/g; s/う/\x1b[0m/g" 15 | } 16 | 17 | function manai() { 18 | local BLUE='\e[34m' 19 | local NC='\e[0m' 20 | 21 | local OPENAI_API_KEY=${MANAI_OPENAI_API_KEY:-"$OPENAI_API_KEY"} 22 | 23 | autoload -Uz read-from-minibuffer 24 | subcommand=$BUFFER 25 | 26 | if [[ -z "$OPENAI_API_KEY" ]]; then 27 | zle beginning-of-line 28 | zle kill-buffer 29 | zle -R 30 | echo "🤖 Please set OPENAI_API_KEY or MANAI_OPENAI_API_KEY" 31 | BUFFER=$subcommand 32 | zle end-of-line 33 | return 34 | fi 35 | 36 | read-from-minibuffer '🤖 What do you want to do?: ' 37 | local requirement=$REPLY 38 | REPLY="" 39 | 40 | if [[ -z $requirement ]]; then 41 | return 42 | fi 43 | 44 | local preview_command="${__manai_base}/lib/manai-preview-format.zsh {2} {4}" 45 | # result=$( cat $HOME/tmp/out.txt | 46 | result=$(OPENAI_API_KEY="${OPENAI_API_KEY}" "${__manai_base}/bin/manai" "$subcommand" "$requirement" | 47 | __manai_format | 48 | fzf --ansi \ 49 | --delimiter='\t' \ 50 | --layout=reverse \ 51 | --bind 'enter:execute(echo -E {3})+abort' \ 52 | --with-nth=1 \ 53 | --preview="$preview_command" \ 54 | --preview-window=right:40%:wrap 55 | ) 56 | zle kill-buffer 57 | zle -R 58 | BUFFER=$result 59 | zle end-of-line 60 | } 61 | zle -N manai 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | manai 2 | ===== 3 |
4 | 5 |
6 | 7 | manai is an AI powered interactive command line completion for Zsh. Hotkey to trigger and ask anything about your work-in-progress command line. 8 | 9 | ![demo](doc/img/demo.gif) 10 | 11 | Prerequisites 12 | ------------- 13 | - [Zsh](https://www.zsh.org/) 14 | - [fzf](https://github.com/junegunn/fzf) 15 | - [OpenAI API Key](https://platform.openai.com) 16 | 17 | Installation 18 | ------------ 19 | 20 | 1. Clone this package to somewhere you are comfortable with (e.g. `$HOME/.dotfiles/manai`) 21 | 22 | ```bash 23 | $ git clone git@github.com:mecab/manai.git $HOME/.dotfiles/manai 24 | ``` 25 | 26 | 2. Download manai binary (why?/you don't trust the binary? see [Build Manai Binary by Yourself](#build-manai-binary-by-yourself)) 27 | 28 | ```bash 29 | $ $HOME/.dotfiles/manai/download-manai.zsh 30 | ``` 31 | 32 | 3. Source `manai.zsh` in your zshrc, set your `OPENAI_API_KEY` then bind `manai` function to any keybind 33 | 34 | ```bash 35 | $ nano ~/.zshrc 36 | ``` 37 | 38 | and add the following 39 | 40 | ``` 41 | export OPENAI_API_KEY="your-openai-api-key" 42 | source $HOME/.dotfiles/manai/manai.zsh 43 | 44 | # bind to `Alt-h`. Update this to your preference! 45 | bindkey '\eh' manai 46 | ``` 47 | 48 | Note: You can also specify `MANAI_OPENAI_API_KEY` instead of `OPENAI_AI_KEY` for `manai` exclusive use. 49 | 50 | 4. then reload your zshrc 51 | 52 | ```bash 53 | $ exec $SHELL -l 54 | ``` 55 | 56 | Build Manai Binary by Yourself 57 | ------------------------------ 58 | 59 | Manai uses a binary executable to process the output from ChatGPT because it is difficult to handle the streamed output using only shell scripts. For convenience, pre-built binaries are distributed, but you can also build it yourself. It is written in TypeScript and can be compiled if you have Bun installed. 60 | 61 | 1. Install JS Dependencies 62 | 63 | ```bash 64 | $ cd $HOME/.dotfiles/manai 65 | $ bun install 66 | ``` 67 | 68 | 2. Build the binary 69 | 70 | ```bash 71 | $ bun run build 72 | ``` 73 | 74 | It should generate `bin/manai` binary for your architecture. 75 | 76 | Why "Manai"? 77 | ------------- 78 | - man + AI 79 | - (悩)まない (naya)manai - "worry-free" in Japanese 80 | -------------------------------------------------------------------------------- /download-manai.zsh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | # Set default version 4 | version="latest" 5 | 6 | # Perform confirmation by default 7 | confirm_download=true 8 | 9 | # Function to normalize version string 10 | function normalize_version() { 11 | local ver=$1 12 | ver=${ver#v.} # Remove leading "v." from version string 13 | if [[ "$ver" != v* ]]; then 14 | ver="v$ver" # Add leading "v" if not present 15 | fi 16 | echo "$ver" 17 | } 18 | 19 | # Parse command line arguments 20 | while getopts ":v:y" opt; do 21 | case $opt in 22 | v) 23 | version=$(normalize_version $OPTARG) 24 | ;; 25 | y) 26 | confirm_download=false 27 | ;; 28 | \?) 29 | echo "Invalid option: -$OPTARG" >&2 30 | exit 1 31 | ;; 32 | esac 33 | done 34 | 35 | # Function to remove temporary directory 36 | function cleanup() { 37 | if [[ -n "$temp_dir" && -d "$temp_dir" ]]; then 38 | rm -rf "$temp_dir" 39 | fi 40 | } 41 | 42 | # Remove temporary directory on script exit or interruption 43 | trap cleanup EXIT INT TERM 44 | 45 | # Function to determine the architecture 46 | function get_architecture() { 47 | local os=$(uname) 48 | local arch=$(uname -m) 49 | 50 | if [[ "$os" == "Linux" ]]; then 51 | if [[ "$arch" == "x86_64" ]]; then 52 | echo "linux-x64" 53 | elif [[ "$arch" == "aarch64" ]]; then 54 | echo "linux-arm64" 55 | else 56 | echo "Unsupported architecture: $arch" >&2 57 | exit 1 58 | fi 59 | elif [[ "$os" == "Darwin" ]]; then 60 | if [[ "$arch" == "x86_64" ]]; then 61 | echo "darwin-x64" 62 | elif [[ "$arch" == "arm64" ]]; then 63 | echo "darwin-arm64" 64 | else 65 | echo "Unsupported architecture: $arch" >&2 66 | exit 1 67 | fi 68 | else 69 | echo "Unsupported operating system: $os" >&2 70 | exit 1 71 | fi 72 | } 73 | 74 | # Create temporary directory 75 | temp_dir=$(mktemp -d) 76 | 77 | # Get the architecture 78 | architecture=$(get_architecture) 79 | 80 | # Set the download URL 81 | if [[ "$version" == "latest" ]]; then 82 | download_url="https://github.com/mecab/manai/releases/latest/download/manai-bun-${architecture}.tar.gz" 83 | else 84 | download_url="https://github.com/mecab/manai/releases/download/${version}/manai-bun-${architecture}.tar.gz" 85 | fi 86 | 87 | # Confirm with the user if needed 88 | if [[ "$confirm_download" == true ]]; then 89 | echo "Detected architecture: $architecture" 90 | echo "Download URL: $download_url" 91 | read "confirm?Do you want to proceed with the download? [y/N]: " 92 | 93 | if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then 94 | echo "Download aborted by the user." 95 | exit 0 96 | fi 97 | fi 98 | 99 | # Download and extract the executable 100 | curl -L "$download_url" | tar -xz -C "$temp_dir" 101 | 102 | # Create the bin directory in the same directory as the script (if it doesn't exist) 103 | script_dir=$(dirname "$0") 104 | bin_dir="$script_dir/bin" 105 | mkdir -p "$bin_dir" 106 | 107 | # Copy the manai binary to the bin directory 108 | cp "$temp_dir/bin/manai" "$bin_dir" 109 | 110 | echo "manai binary (version: $version) has been successfully downloaded and installed in the bin directory." 111 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 2 | 3 | # Logs 4 | 5 | logs 6 | _.log 7 | npm-debug.log_ 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Caches 14 | 15 | .cache 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | 19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 20 | 21 | # Runtime data 22 | 23 | pids 24 | _.pid 25 | _.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | 34 | coverage 35 | *.lcov 36 | 37 | # nyc test coverage 38 | 39 | .nyc_output 40 | 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 42 | 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | 47 | bower_components 48 | 49 | # node-waf configuration 50 | 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | 55 | build/Release 56 | 57 | # Dependency directories 58 | 59 | node_modules/ 60 | jspm_packages/ 61 | 62 | # Snowpack dependency directory (https://snowpack.dev/) 63 | 64 | web_modules/ 65 | 66 | # TypeScript cache 67 | 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | 72 | .npm 73 | 74 | # Optional eslint cache 75 | 76 | .eslintcache 77 | 78 | # Optional stylelint cache 79 | 80 | .stylelintcache 81 | 82 | # Microbundle cache 83 | 84 | .rpt2_cache/ 85 | .rts2_cache_cjs/ 86 | .rts2_cache_es/ 87 | .rts2_cache_umd/ 88 | 89 | # Optional REPL history 90 | 91 | .node_repl_history 92 | 93 | # Output of 'npm pack' 94 | 95 | *.tgz 96 | 97 | # Yarn Integrity file 98 | 99 | .yarn-integrity 100 | 101 | # dotenv environment variable files 102 | 103 | .env 104 | .env.development.local 105 | .env.test.local 106 | .env.production.local 107 | .env.local 108 | 109 | # parcel-bundler cache (https://parceljs.org/) 110 | 111 | .parcel-cache 112 | 113 | # Next.js build output 114 | 115 | .next 116 | out 117 | 118 | # Nuxt.js build / generate output 119 | 120 | .nuxt 121 | dist 122 | 123 | # Gatsby files 124 | 125 | # Comment in the public line in if your project uses Gatsby and not Next.js 126 | 127 | # https://nextjs.org/blog/next-9-1#public-directory-support 128 | 129 | # public 130 | 131 | # vuepress build output 132 | 133 | .vuepress/dist 134 | 135 | # vuepress v2.x temp and cache directory 136 | 137 | .temp 138 | 139 | # Docusaurus cache and generated files 140 | 141 | .docusaurus 142 | 143 | # Serverless directories 144 | 145 | .serverless/ 146 | 147 | # FuseBox cache 148 | 149 | .fusebox/ 150 | 151 | # DynamoDB Local files 152 | 153 | .dynamodb/ 154 | 155 | # TernJS port file 156 | 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | 161 | .vscode-test 162 | 163 | # yarn v2 164 | 165 | .yarn/cache 166 | .yarn/unplugged 167 | .yarn/build-state.yml 168 | .yarn/install-state.gz 169 | .pnp.* 170 | 171 | # IntelliJ based IDEs 172 | .idea 173 | 174 | # Finder (MacOS) folder config 175 | .DS_Store 176 | 177 | # Not needed for bun 178 | package-lock.json 179 | 180 | ### Project specific ignore below ### 181 | 182 | # Compiled binary 183 | bin/ 184 | -------------------------------------------------------------------------------- /.github/workflows/build-and-release.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | create-release: 10 | runs-on: ubuntu-latest 11 | outputs: 12 | release_id: ${{ steps.set_release_output.outputs.id }} 13 | upload_url: ${{ steps.set_release_output.outputs.upload_url }} 14 | 15 | steps: 16 | - name: Get Release 17 | id: get_release 18 | uses: joutvhu/get-release@v1 19 | with: 20 | tag_name: ${{ github.ref_name }} 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | 24 | - name: Create Release 25 | id: create_release 26 | if: ${{ steps.get_release.outputs.id == '' }} 27 | uses: actions/create-release@v1 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | with: 31 | tag_name: ${{ github.ref_name }} 32 | release_name: Release ${{ github.ref_name }} 33 | draft: false 34 | prerelease: false 35 | 36 | - name: Set Release Output 37 | id: set_release_output 38 | run: | 39 | echo "id=${{ steps.get_release.outputs.id || steps.create_release.outputs.id }}" >> $GITHUB_OUTPUT 40 | echo "upload_url=${{ steps.get_release.outputs.upload_url || steps.create_release.outputs.upload_url }}" >> $GITHUB_OUTPUT 41 | 42 | build: 43 | runs-on: ubuntu-latest 44 | 45 | strategy: 46 | matrix: 47 | target: [bun-linux-x64, bun-linux-arm64, bun-darwin-x64, bun-darwin-arm64] 48 | 49 | steps: 50 | - name: Checkout code 51 | uses: actions/checkout@v3 52 | 53 | - name: Set up Bun 54 | uses: oven-sh/setup-bun@v1 55 | with: 56 | bun-version: 1.1.9 57 | 58 | - name: Install dependencies 59 | run: bun install 60 | 61 | - name: Build binary 62 | run: bun build ./app.ts --compile --outfile bin/manai --target=${{ matrix.target }} 63 | 64 | - name: Compress binary 65 | run: tar -czf manai-${{ matrix.target }}.tar.gz bin/ 66 | 67 | - name: Upload artifact 68 | uses: actions/upload-artifact@v3 69 | with: 70 | name: manai-${{ matrix.target }} 71 | path: manai-${{ matrix.target }}.tar.gz 72 | 73 | release: 74 | needs: [create-release, build] 75 | runs-on: ubuntu-latest 76 | strategy: 77 | matrix: 78 | target: [bun-linux-x64, bun-linux-arm64, bun-darwin-x64, bun-darwin-arm64] 79 | 80 | steps: 81 | - name: Download artifact 82 | uses: actions/download-artifact@v3 83 | with: 84 | name: manai-${{ matrix.target }} 85 | 86 | - name: Get Release Assets 87 | id: get_assets 88 | env: 89 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 90 | run: | 91 | assets=$(gh api /repos/${{ github.repository }}/releases/${{ needs.create-release.outputs.release_id }}/assets | jq -r '.[] | select(.name == "manai-${{ matrix.target }}.tar.gz") | .id') 92 | echo "assets=${assets}" >> $GITHUB_OUTPUT 93 | 94 | - name: Delete Existing Asset 95 | if: ${{ steps.get_assets.outputs.assets != '' }} 96 | env: 97 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 98 | run: | 99 | gh api /repos/${{ github.repository }}/releases/assets/${{ steps.get_assets.outputs.assets }} -X DELETE 100 | 101 | - name: Upload Release Asset 102 | uses: actions/upload-release-asset@v1 103 | env: 104 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 105 | with: 106 | upload_url: ${{ needs.create-release.outputs.upload_url }} 107 | asset_path: manai-${{ matrix.target }}.tar.gz 108 | asset_name: manai-${{ matrix.target }}.tar.gz 109 | asset_content_type: application/gzip -------------------------------------------------------------------------------- /app.ts: -------------------------------------------------------------------------------- 1 | import readline from 'readline'; 2 | import * as Diff from 'diff'; 3 | import OpenAI from 'openai'; 4 | 5 | // TODO: Better markers for diff e.g. invisible unicode characters 6 | const ADD_START='追追追'; 7 | const DEL_START='削削削'; 8 | const DIFF_END='終終終'; 9 | 10 | function question(query: string): Promise { 11 | const rl = readline.createInterface({ 12 | input: process.stdin, 13 | output: process.stdout 14 | }); 15 | 16 | return new Promise(resolve => rl.question(query, answer => { 17 | rl.close(); 18 | resolve(answer); 19 | })); 20 | } 21 | 22 | function buildPrompt(partialCommand: string, requirement: string): string { 23 | const prompt = ` 24 | Below is a partial command. 25 | 26 | ${partialCommand} 27 | 28 | Here are the requirements. 29 | 30 | 「${requirement}」 31 | 32 | Infer the intent behind the requirements and suggest up to 5 modifications to the command line. Separate each suggestion with a new line and format each line as follows: 33 | 34 | command_part@@@description@@@full_command 35 | 36 | Where: 37 | 38 | - command_part: The part of the command line to add (if removing, use remove: followed by the part to be removed) 39 | - description: A description of the modification 40 | - full_command: The complete command line. 41 | 42 | First, you think the full_command to satisfy the requirements. Then you can think command_part where highlights the most important parts of your modification. Finally, you can write a description of the modification. 43 | Please output only command_part is relevant suggestion to the requirements. 44 | 45 | Strictly maintain the order of the columns. Only use this format for the output. Do not enclose the output in a Markdown code block.`; 46 | return prompt; 47 | } 48 | 49 | function processDiff(partialCommand: string, completeCommand: string): string { 50 | const diffColoredStr = Diff.diffWordsWithSpace(partialCommand, completeCommand, {ignoreWhitespace: true}).map(part => { 51 | if (part.added) { 52 | return `${ADD_START}${part.value}${DIFF_END}`; 53 | } 54 | if (part.removed) { 55 | return `${DEL_START}${part.value}${DIFF_END}`; 56 | } 57 | return part.value; 58 | }).join(''); 59 | 60 | return diffColoredStr; 61 | } 62 | 63 | function toTsv(partialCommand: string, aiLine: string): string { 64 | // just in case AI outputs more than four fields, we concats the rest as completeCommand 65 | const [commandPart, description, ...rest] = aiLine.split('@@@'); 66 | const completeCommand = rest.join(''); 67 | const diffColoredStr = processDiff(partialCommand, completeCommand); 68 | const tsv = [commandPart, description, completeCommand, diffColoredStr].join('\t'); 69 | 70 | return tsv 71 | } 72 | 73 | async function main() { 74 | const partialCommand = process.argv[2]; 75 | const requirement = process.argv[3]; 76 | const ans = requirement ? requirement : await question('What do you want to do: '); 77 | 78 | const prompt = buildPrompt(partialCommand, ans); 79 | 80 | const openai = new OpenAI(); 81 | const stream = openai.beta.chat.completions.stream({ 82 | model: process.env.MANAI_MODEL ?? 'gpt-4o', 83 | messages: [ 84 | { role: 'system', content: prompt }, 85 | ], 86 | stream: true, 87 | }); 88 | 89 | // const completion = await stream.finalContent(); 90 | // const filteredLines = completion?.split('\n').map(e => e.replaceAll('@@@', '\t')).filter((line) => line.length > 0); 91 | // console.log(filteredLines?.join('\n')); 92 | 93 | let buffer: string = ""; 94 | 95 | stream.on('content', (contentDelta) => { 96 | buffer += contentDelta; 97 | const lines = buffer.split('\n'); 98 | const completeLines = lines.slice(0, -1); 99 | const incompleteLine = lines.slice(-1)[0]; 100 | 101 | for (const line of completeLines) { 102 | if (line.length === 0) { 103 | continue; 104 | } 105 | 106 | console.log(toTsv(partialCommand, line)); 107 | } 108 | 109 | buffer = incompleteLine; 110 | }); 111 | 112 | await stream.finalContent(); 113 | if (buffer.length > 0) { 114 | console.log(toTsv(partialCommand, buffer)); 115 | } 116 | } 117 | 118 | main(); 119 | -------------------------------------------------------------------------------- /doc/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Layer 1 4 | 5 | Created with Fabric.js 5.3.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | --------------------------------------------------------------------------------