xenodium / chatgpt-shell

ChatGPT and DALL-E Emacs shells + Org babel 🦄 + a shell maker for other providers
https://xenodium.com
GNU General Public License v3.0
782 stars 66 forks source link
chatgpt dall-e emacs org-mode

👉 [[https://github.com/sponsors/xenodium][Support this work via GitHub Sponsors]]

[[https://melpa.org/#/chatgpt-shell][file:https://melpa.org/packages/chatgpt-shell-badge.svg]]

ChatGPT and DALL-E Emacs shells + [[https://orgmode.org/worg/org-contrib/babel/intro.html][Org Babel]].

Includes =shell-maker=, a way to create shells for any service (local or cloud).

** Support this effort

If you're finding =chatgpt-shell= useful, consider ✨[[https://github.com/sponsors/xenodium][sponsoring]]✨.

=chatgpt-shell= is in development. Please report issues or send [[https://github.com/xenodium/chatgpt-shell/pulls][pull requests]] for improvements.

** Like this package? Tell me about it 💙

Finding it useful? Like the package? I'd love to hear from you. Get in touch ([[https://indieweb.social/@xenodium][Mastodon]] / [[https://twitter.com/xenodium][Twitter]] / [[https://www.reddit.com/user/xenodium][Reddit]] / [[mailto:meATxenodium.com][Email]]).

** Shell usage

+HTML:

+HTML:

** Insert to current buffer

+HTML:

** MELPA

If using [[https://github.com/jwiegley/use-package][use-package]], you can install with =:ensure t=.

+begin_src emacs-lisp :lexical no

(use-package chatgpt-shell :ensure t :custom ((chatgpt-shell-openai-key (lambda () (auth-source-pass-get 'secret "openai-key")))))

+end_src

** Straight

=chatgpt-shell= depends on =shell-maker=. This dependency is resolved without issues on MELPA but seems to run into issues with =straight=. I'm not familiar with =straight= but users have reported the following to work.

+begin_src emacs-lisp :lexical no

(use-package shell-maker :straight (:host github :repo "xenodium/chatgpt-shell" :files ("shell-maker.el")))

(use-package chatgpt-shell :requires shell-maker :straight (:host github :repo "xenodium/chatgpt-shell" :files ("chatgpt-shell.el")))

+end_src

If you have a better =straight= solution, please send a pull request or open an issue with a suggestion.

Read on for setting your OpenAI key in other ways.

You'll first need to get a [[https://platform.openai.com/account/api-keys][key from OpenAI]].

** ChatGPT key *** As function

+begin_src emacs-lisp

;; if you are using the "pass" password manager (setq chatgpt-shell-openai-key (lambda () ;; (auth-source-pass-get 'secret "openai-key") ; alternative using pass support in auth-sources (nth 0 (process-lines "pass" "show" "openai-key"))))

;; or if using auth-sources, e.g., so the file ~/.authinfo has this line: ;; machine api.openai.com password OPENAI_KEY (setq chatgpt-shell-openai-key (auth-source-pick-first-password :host "api.openai.com"))

;; or same as previous but lazy loaded (prevents unexpected passphrase prompt) (setq chatgpt-shell-openai-key (lambda () (auth-source-pick-first-password :host "api.openai.com")))

+end_src

*** Manually =M-x set-variable chatgpt-shell-openai-key=

*** As variable

+begin_src emacs-lisp

(setq chatgpt-shell-openai-key "my key")

+end_src

*** As an ENV variable

+begin_src emacs-lisp

(setq chatgpt-shell-openai-key (getenv "OPENAI_API_KEY"))

+end_src

** DALL-E key

Same as ChatGPT, but use =dall-e-shell-openai-key= variable.

If you use ChatGPT through proxy service "https://api.chatgpt.domain.com", set options like the following:

+begin_src emacs-lisp :lexical no

(use-package chatgpt-shell :ensure t :custom ((chatgpt-shell-api-url-base "https://api.chatgpt.domain.com") (chatgpt-shell-openai-key (lambda () ;; Here the openai-key should be the proxy service key. (auth-source-pass-get 'secret "openai-key")))))

+end_src

If your proxy service API path is not OpenAI ChatGPT default path like "=/v1/chat/completions=", then you can customize option ~chatgpt-shell-api-url-path~.

Behind the scenes chatgpt-shell uses =curl= to send requests to the openai server. If you use ChatGPT through a HTTP proxy (for example you are in a corporate network and a HTTP proxy shields the corporate network from the internet), you need to tell =curl= to use the proxy via the curl option =-x http://your_proxy=. One way to do this is to set the proxy url via the customizable variable =chatgpt-shell-additional-curl-options=. If you set this variable via the Emacs Customize interface you should insert two separate items =-x= and =http://your_proxy=. See the curl manpage for more details and further options.

Endpoint: =https://{your-resource-name}.openai.azure.com/openai/deployments/{deployment-id}/chat/completions?api-version={api-version}=

Configure the following variables:

+begin_src emacs-lisp

(setq chatgpt-shell-api-url-base "https://{your-resource-name}.openai.azure.com") (setq chatgpt-shell-api-url-path "/openai/deployments/{deployment-id}/chat/completions?api-version={api-version}") (setq chatgpt-shell-auth-header (lambda () (format "api-key: %s" (chatgpt-shell-openai-key))))

+end_src

Launch with =M-x chatgpt-shell= or =dall-e-shell=.

Note: =M-x chatgpt-shell= or =dall-e-shell= keeps a single shell around, refocusing if needed. To launch multiple shells, use =C-u M-x chatgpt-shell=.

Type =clear= as a prompt.

+begin_src sh

ChatGPT> clear

+end_src

Alternatively, use either =M-x chatgpt-shell-clear-buffer= or =M-x comint-clear-buffer=.

Save with =M-x shell-maker-save-session-transcript= and restore with =M-x chatgpt-shell-restore-session-from-transcript=.

=chatgpt-shell= can either wait until the entire response is received before displaying, or it can progressively display as chunks arrive (streaming).

Streaming is enabled by default. =(setq chatgpt-shell-streaming nil)= to disable it.

+BEGIN_SRC emacs-lisp :results table :colnames '("Custom variable" "Description") :exports results

(let ((rows)) (mapatoms (lambda (symbol) (when (and (string-match "^chatgpt-shell" (symbol-name symbol)) (custom-variable-p symbol)) (push `(,symbol ,(car (split-string (or (get (indirect-variable symbol) 'variable-documentation) (get symbol 'variable-documentation) "") "\n"))) rows)))) rows)

+END_SRC

+RESULTS:

| Custom variable | Description | |------------------------------------------------------------------+------------------------------------------------------------------------------| | chatgpt-shell-prompt-compose-view-mode-hook | Hook run after entering or leaving chatgpt-shell-prompt-compose-view-mode'. | | chatgpt-shell-display-function | Function to display the shell. Set todisplay-buffer' or custom function. | | chatgpt-shell-model-versions | The list of ChatGPT OpenAI models to swap from. | | chatgpt-shell-system-prompt | The system prompt chatgpt-shell-system-prompts' index. | | chatgpt-shell-default-prompts | List of default prompts to choose from. | | chatgpt-shell-read-string-function | Function to read strings from user. | | chatgpt-shell-model-temperature | What sampling temperature to use, between 0 and 2, or nil. | | chatgpt-shell-transmitted-context-length | Controls the amount of context provided to chatGPT. | | chatgpt-shell-system-prompts | List of system prompts to choose from. | | chatgpt-shell-streaming | Whether or not to stream ChatGPT responses (show chunks as they arrive). | | chatgpt-shell-prompt-header-refactor-code | Prompt header ofrefactor-code. | | chatgpt-shell-auth-header | Function to generate the request'sAuthorization' header string. | | chatgpt-shell-prompt-header-whats-wrong-with-last-command | Prompt header of whats-wrong-with-last-command. | | chatgpt-shell-prompt-header-write-git-commit | Prompt header of git-commit. | | chatgpt-shell-logging | Logging disabled by default (slows things down). | | chatgpt-shell-prompt-query-response-style | Determines the prompt style when invoking from other buffers. | | chatgpt-shell-root-path | Root path location to store internal shell files. | | chatgpt-shell-prompt-header-proofread-region | Promt header of proofread-region. | | chatgpt-shell-model-version | The active ChatGPT OpenAI model index. | | chatgpt-shell-source-block-actions | Block actions for known languages. | | chatgpt-shell-insert-dividers | Whether or not to display a divider between requests and responses. | | chatgpt-shell-prompt-header-eshell-summarize-last-command-output | Prompt header of eshell-summarize-last-command-output. | | chatgpt-shell-welcome-function | Function returning welcome message or nil for no message. | | chatgpt-shell-api-url-path | OpenAI API's URL path. | | chatgpt-shell-additional-curl-options | Additional options for curl' command. | | chatgpt-shell-openai-key | OpenAI key as a string or a function that loads and returns it. | | chatgpt-shell-after-command-functions | Abnormal hook (i.e. with parameters) invoked after each command. | | chatgpt-shell-prompt-header-describe-code | Prompt header ofdescribe-code. | | chatgpt-shell-api-url-base | OpenAI API's base URL. | | chatgpt-shell-babel-headers | Additional headers to make babel blocks work. | | chatgpt-shell-highlight-blocks | Whether or not to highlight source blocks. | | chatgpt-shell-language-mapping | Maps external language names to Emacs names. | | chatgpt-shell-prompt-header-generate-unit-test | Prompt header ofgenerate-unit-test`. | | chatgpt-shell-request-timeout | How long to wait for a request to time out in seconds. |

There are more. Browse via =M-x set-variable=

** =chatgpt-shell-display-function= (with custom function)

If you'd prefer your own custom display function,

+begin_src emacs-lisp :lexical no

(setq chatgpt-shell-display-function #'my/chatgpt-shell-frame)

(defun my/chatgpt-shell-frame (bname) (let ((cur-f (selected-frame)) (f (my/find-or-make-frame "chatgpt"))) (select-frame-by-name "chatgpt") (pop-to-buffer-same-window bname) (set-frame-position f (/ (display-pixel-width) 2) 0) (set-frame-height f (frame-height cur-f)) (set-frame-width f (frame-width cur-f) 1)))

(defun my/find-or-make-frame (fname) (condition-case nil (select-frame-by-name fname) (error (make-frame `((name . ,fname))))))

+end_src

Thanks to [[https://github.com/tuhdo][tuhdo]] for the custom display function.

+RESULTS:

| Binding | Command | Description | |-----------------+-----------------------------------------------------+-----------------------------------------------------------------------------------------------| | | chatgpt-shell | Start a ChatGPT shell interactive command. | | | chatgpt-shell-rename-block-at-point | Rename block at point (perhaps a different language). | | | chatgpt-shell-prompt-compose-view-mode | Like view-mode, but extended for ChatGPT Compose. | | C-M-h | chatgpt-shell-mark-at-point-dwim | Mark source block if at point. Mark all output otherwise. | | | chatgpt-shell-prompt-compose-quit-and-close-frame | Quit compose and close frame if it's the last window. | | C- or M-p | chatgpt-shell-previous-input | Cycle backwards through input history, saving input. | | | chatgpt-shell-execute-babel-block-action-at-point | Execute block as org babel. | | | chatgpt-shell-eshell-whats-wrong-with-last-command | Ask ChatGPT what's wrong with the last eshell command. | | C-c C-p | chatgpt-shell-previous-item | Go to previous item. | | | chatgpt-shell-set-as-primary-shell | Set as primary shell when there are multiple sessions. | | | chatgpt-shell-prompt-compose-send-buffer | Send compose buffer content to shell for processing. | | | chatgpt-shell-refresh-rendering | Refresh markdown rendering by re-applying to entire buffer. | | | chatgpt-shell-prompt-compose-next-history | Insert next prompt from history into compose buffer. | | | chatgpt-shell-explain-code | Describe code from region using ChatGPT. | | | chatgpt-shell-prompt-compose-other-buffer | Jump to the shell buffer (compose's other buffer). | | | chatgpt-shell-rename-buffer | Rename current shell buffer. | | | chatgpt-shell-prompt-compose-previous-block | Jump to and select previous code block. | | | chatgpt-shell-write-git-commit | Write commit from region using ChatGPT. | | | chatgpt-shell-prompt | Make a ChatGPT request from the minibuffer. | | | chatgpt-shell-remove-block-overlays | Remove block overlays. Handy for renaming blocks. | | | chatgpt-shell-system-prompts-menu | ChatGPT | | | chatgpt-shell-prompt-compose-mode | Major mode for composing ChatGPT prompts from a dedicated buffer. | | | chatgpt-shell-proofread-region | Proofread English from region using ChatGPT. | | | chatgpt-shell-prompt-compose-retry | Retry sending request to shell. | | M-r | chatgpt-shell-search-history | Search previous input history. | | | chatgpt-shell-send-and-review-region | Send region to ChatGPT, review before submitting. | | C- or M-n | chatgpt-shell-next-input | Cycle forwards through input history. | | | chatgpt-shell-delete-interaction-at-point | Delete interaction (request and response) at point. | | | chatgpt-shell-eshell-summarize-last-command-output | Ask ChatGPT to summarize the last command output. | | | chatgpt-shell-prompt-appending-kill-ring | Make a ChatGPT request from the minibuffer appending kill ring. | | | chatgpt-shell-prompt-other-buffer-response-mode | Major mode for buffers created by other-buffer'chatgpt-shell-prompt-query-response-style'. | | | chatgpt-shell-describe-code | Describe code from region using ChatGPT. | | | chatgpt-shell-mode | Major mode for ChatGPT shell. | | C-c C-v | chatgpt-shell-swap-model-version | Swap model version from chatgpt-shell-model-versions'. | | | chatgpt-shell-previous-source-block | Move point to previous source block. | | | chatgpt-shell-prompt-compose-search-history | Search prompt history, select, and insert to current compose buffer. | | | chatgpt-shell-refactor-code | Refactor code from region using ChatGPT. | | S-<return> | chatgpt-shell-newline | Insert a newline, and move to left margin of the new line. | | C-c C-s | chatgpt-shell-swap-system-prompt | Swap system prompt fromchatgpt-shell-system-prompts'. | | C-x C-s | chatgpt-shell-save-session-transcript | Save shell transcript to file. | | C-c M-o | chatgpt-shell-clear-buffer | Clear the comint buffer. | | | chatgpt-shell-load-awesome-prompts | Load chatgpt-shell-system-prompts' from awesome-chatgpt-prompts. | | | chatgpt-shell-prompt-compose-request-entire-snippet | If the response code is incomplete, request the entire snippet. | | RET | chatgpt-shell-submit | Submit current input. | | C-c C-n | chatgpt-shell-next-item | Go to next item. | | | chatgpt-shell-describe-image | Request OpenAI to describe image. | | | chatgpt-shell-execute-block-action-at-point | Execute block at point. | | | chatgpt-shell-view-at-point | View prompt and output at point in a separate buffer. | | | chatgpt-shell-send-region | Send region to ChatGPT. | | | chatgpt-shell-restore-session-from-transcript | Restore session from transcript. | | | chatgpt-shell-prompt-compose-next-block | Jump to and select next code block. | | | chatgpt-shell-mark-block | Mark current block in compose buffer. | | | chatgpt-shell-prompt-compose-reply | Reply as a follow-up and compose another query. | | | chatgpt-shell-generate-unit-test | Generate unit-test for the code from region using ChatGPT. | | | chatgpt-shell-prompt-compose-previous-history | Insert previous prompt from history into compose buffer. | | C-c C-e | chatgpt-shell-prompt-compose | Compose and send prompt from a dedicated buffer. | | | chatgpt-shell-next-source-block | Move point to previous source block. | | C-c C-c | chatgpt-shell-ctrl-c-ctrl-c | If point in source block, execute it. Otherwise interrupt. | | | chatgpt-shell-prompt-compose-cancel | Cancel and close compose buffer. | | | chatgpt-shell-interrupt | Interruptchatgpt-shell' from any buffer. |

Browse all available via =M-x=.

+RESULTS:

| Custom variable | Description | |--------------------------------------+-----------------------------------------------------------------------------| | dall-e-shell-welcome-function | Function returning welcome message or nil for no message. | | dall-e-shell-openai-key | OpenAI key as a string or a function that loads and returns it. | | dall-e-shell-image-size | The default size of the requested image as a string. | | dall-e-shell-read-string-function | Function to read strings from user. | | dall-e-shell-request-timeout | How long to wait for a request to time out. | | dall-e-shell-model-version | The used DALL-E OpenAI model. For Dall-E 3, use "dall-e-3". | | dall-e-shell-display-function | Function to display the shell. Set to display-buffer' or custom function. | | dall-e-shell-model-versions | The list of Dall-E OpenAI models to swap from. | | dall-e-shell-additional-curl-options | Additional options forcurl' command. | | dall-e-shell-image-output-directory | Output directory for the generated image. | | dall-e-shell-image-quality | Image quality: standard' orhd' (DALL-E 3 only feature). |

+RESULTS:

| C- or M-p | dall-e-shell-previous-input | Cycle backwards through input history, saving input. | | | dall-e-shell | Start a DALL-E shell. | | | dall-e-shell-insert-image-from-region-description | Generate and insert an image using current region as description. | | | dall-e-shell-interrupt | Interrupt dall-e-shell' from any buffer. | | S-<return> | dall-e-shell-newline | Insert a newline, and move to left margin of the new line. | | RET | dall-e-shell-submit | Submit current input. | | C-x C-s | dall-e-shell-save-session-transcript | Save shell transcript to file. | | C-c C-v | dall-e-shell-swap-model-version | Swap model version fromdall-e-shell-model-versions'. | | | dall-e-shell-mode | Major mode for DALL-E shell. | | C- or M-n | dall-e-shell-next-input | Cycle forwards through input history. | | M-r | dall-e-shell-search-history | Search previous input history. | | | dall-e-shell-rename-buffer | Rename current shell buffer. |

Load =(require 'ob-chatgpt-shell)= and invoke =(ob-chatgpt-shell-setup)=.

+begin_src org

,#+begin_src chatgpt-shell Hello ,#+end_src

,#+RESULTS: : Hi there! How can I assist you today?

+end_src

** :version

Use =:version= to specify "gpt-4", "gpt-3.5-turbo", or something else.

+begin_src org

,#+begin_src chatgpt-shell :version "gpt-4" Hello ,#+end_src

,#+RESULTS: Hello! How can I help you today?

+end_src

** :system

Use =:system= to set the system prompt.

+begin_src org

,#+begin_src chatgpt-shell :system "always respond like a pirate" hello ,#+end_src

,#+RESULTS: Ahoy there, me hearty! How be ye today?

+end_src

** :temperature

Use =:temperature= to set the [[https://platform.openai.com/docs/api-reference/completions/create#completions/create-temperature][temperature]] parameter.

+begin_src org

,#+begin_src chatgpt-shell :temperature 0.3 hello ,#+end_src

+end_src

** :context

Use =:context t= to include all prior context in current buffer.

+begin_src org

,#+begin_src chatgpt-shell tell me a random day of the week ,#+end_src

,#+RESULTS: Wednesday

,#+begin_src chatgpt-shell :system "always respond like a pirate" hello ,#+end_src

,#+RESULTS: Ahoy there, me hearty! How be ye today?

,#+begin_src chatgpt-shell :context t what was the day you told me and what greeting? ,#+end_src

,#+RESULTS: The day I told you was Wednesday, and the greeting I used was "Ahoy there, me hearty! How be ye today?"

+end_src

If you'd like to cherrypick which blocks are part of a given context, add =:context CONTEXT-NAME= to each block where CONTEXT-NAME is any string. When this form is used only source blocks with same =CONTEXT-NAME= will be included as opposed to every previous block when using =:context t=.

The example below shows how two different contexts can be interleaved.

+begin_src org

,#+begin_src chatgpt-shell :context shakespeare :system "alway speak like shakespeare" How do you do? ,#+end_src

+RESULTS:

How dost thou fare?

,#+begin_src chatgpt-shell :context robot :system "always speak like a sci fi movie robot" How do you do? ,#+end_src

,#+RESULTS: Greetings, human. I am functioning at optimal capacity. How may I assist you in your endeavors today?

,#+begin_src chatgpt-shell :context shakespeare What did you call me? ,#+end_src

,#+RESULTS: Mine apologies if mine words hath caused confusion. I merely addressed thee as 'sir' or 'madam', a term of respect in the language of the Bard. Pray, how may I assist thee further?

+end_src

Load =(require 'ob-dall-e-shell)= and invoke =(ob-dall-e-shell-setup)=.

+begin_src org

,#+begin_src dall-e-shell Pretty clouds ,#+end_src

,#+RESULTS: [[file:/var/folders/m7/ky091cp56d5g68nyhl4y7frc0000gn/T/1680644778.png]]

+end_src

** :version

Use =:version= to set the model, for example: "dall-e-3".

** :results

For DALL-E 3, use =:results both= to also output the revised prompt.

There are currently two shell implementations (ChatGPT and DALL-E). Other services (local or cloud) can be brought to Emacs as shells. =shell-maker= can help with that.

=shell-maker= is a convenience wrapper around [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Shell-Prompts.html][comint mode]].

Both =chatgpt-shell= and =dall-e-shell= use =shell-maker=, but a basic implementation of a new shell looks as follows:

+begin_src emacs-lisp :lexical no

(require 'shell-maker)

(defvar greeter-shell--config (make-shell-maker-config :name "Greeter" :execute-command (lambda (command _history callback error-callback) (funcall callback (format "Hello \"%s\"" command) nil))))

(defun greeter-shell () "Start a Greeter shell." (interactive) (shell-maker-start greeter-shell--config))

+end_src

+HTML:

👉 Find my work useful? [[https://github.com/sponsors/xenodium][Support this work via GitHub Sponsors]] or [[https://apps.apple.com/us/developer/xenodium-ltd/id304568690][buy my iOS apps]].

+HTML:

+HTML:

+HTML:

Made with [[https://contrib.rocks][contrib.rocks]].