├── 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 | 
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 |
--------------------------------------------------------------------------------