What is Pen.el?

Pen.el stands for Prompt Engineering in Emacs. It’s an IDE for Prompt-Engineering, a new type of programming, and the project integrates functions based on prompts (i.e. prompt functions) into emacs. Emacs is a human-designed, intelligible user interface with its initial version in 1976. Therefore, the user interface has been maturing for over 45 years. Pen.el takes advantage of this epic legacy of UI design to frame LMs in a powerful way to the user.

Introduction

As a beginner, to get started I would aim to simply set up the pen standalone application, which will provide you with most functionality via a docker container.

This enables you do use pen without integrating it tightly into your own emacs, whereby you can still use the prompting functions in a basic way; The thin-client prompting function names all start with pen-fn- and the names of the fully-featured prompting functions all begin with pf-.

But you will not be able to use the ilambda functions, or get the full Pen experience without integrating Pen.el into your main emacs configuration.

However, if you’re going to test out pen and plan on only setting up the standalone application along with the thin-client, you’ll still be able to use the following applications:

Application name Script name Purpose URL
Apostrophe apostrophe Imaginary conversation https://semiosis.github.io/apostrophe/
LookingGlass lg Imaginary-Web Browser https://semiosis.github.io/looking-glass/
Paracosm cosm AI-Assisted Mind-map https://semiosis.github.io/paracosm/
ComplexTerm ct Real+Imaginary Terminal Multiplexer https://semiosis.github.io/cterm/
ImaginaryInterpreter ii Fully imaginary language interpreter https://semiosis.github.io/ii/
pen bash interop penf Prompt function interface for bash Pen.el host interop
NL·SH nlsh Natural Language Shell https://semiosis.github.io/nlsh/
Pen.el pen Prompt Engineering in Emacs https://semiosis.github.io/pen/

They are accessible by running the script name on the host (or inside the container shell, if you prefer).

Initial setup

These instructions are designed for a linux distribution with docker installed.

Latest guide
https://semiosis.github.io/posts/pen-el-installation-from-scratch/

You will need at bare minimum an OpenAI key, AI21 key or Cohere key.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Start by installing docker!
# https://docs.docker.com/get-docker/

git clone "https://github.com/semiosis/pen.el"
git clone "https://github.com/semiosis/prompts"
git clone "https://github.com/semiosis/engines"
git clone "https://github.com/semiosis/pen-contrib.el"

mkdir -p ~/.pen

# Put in your keys, or do not, it's up to you! Get your free keys from the following URLs
echo "sk-<openai key here>" > ~/.pen/openai_api_key       # https://openai.com/
echo "<ai21 key here>" > ~/.pen/ai21_api_key              # https://www.ai21.com/
echo "<hf key here>" > ~/.pen/hf_api_key                  # https://huggingface.co/
echo "<nlpcloud key here>" > ~/.pen/nlpcloud_api_key      # https://nlpcloud.io/
echo "<alephalpha key here>" > ~/.pen/alephalpha_api_key  # https://aleph-alpha.de/
echo "<cohere key here>" > ~/.pen/cohere_api_key          # https://cohere.ai/

# Add the scripts to the PATH
echo export PATH="$(realpath .)/pen.el/scripts:\$PATH" >> ~/.profile

# Add this to prevent C-s from freezing the terminal
echo "stty stop undef 2>/dev/null; stty start undef 2>/dev/null" | tee -a ~/.zshrc >> ~/.bashrc

# Source your .profile
. ~/.profile

# Run pen - This will start 'pen' emacs inside of a docker container.
# If you are missing keys, just press enter to ignore them!
pen

Upon first running, it will ask you to create the ~/.pen directory. Because the docker container creates this directory on the host, it will be accessible only to the root user.

1
2
# But you may freely chown it to access it
sudo chown -R $USER:USER ~/.pen

Then you may copy the following config file to ~/.pen/pen.yaml as an example.

http://github.com/semiosis/pen.el/blob/master/config/example-pen.yaml

If you ever get an error such as:

1
docker: Error response from daemon: Conflict. The container name "/pen" is already in use by container "...". You have to remove (or rename) that container to be able to reuse that name.`.

Try running this to stop the docker container first, and try again. You shouldn’t run into that issue under normal circumstances, though.

1
docker container stop pen

Force the engine

If you have an OpenAI key, then most prompts/features/applications will simply work without further setup. If you don’t have an OpenAI key, but have another key such as AI21 then you can force all prompts to use the key you have.

I will demonstrate forcing Pen.el to use the Cohere engine for everything. This is useful if you only have a Cohere key.

1
2
# Run this on your host machine
pen config

Ensure this is enabled:

1
(setq pen-prompt-force-engine-disabled t)

And this is set:

1
(setq pen-force-engine "Cohere")

You can use it like a regular editor

This way, it will create a hardlink to the file, so emacs in the docker container can access it.

1
pen yas.el

Basic demo

This is prompting

1
echo "Q: 'Why did the chicken cross the road?' A: '" | pen-prompt | head -n 1 | sed "s/'.*//"
To get to the other side.

Pen.el thin client

This lets you put basic prompt functions into your own emacs without loading the whole of pen.el.

1
(pen-fn-translate/3 (buffer-substring (region-beginning) (region-end)) "English" "French")

Pen.el full install into your own .emacs.d

This is for advanced users, really.

pen server (Using the standalone application, pen)

pen has a standalone application which is an emacs inside docker. This also functions as a server.

1
2
3
4
5
# interact with pen as an editor
pen /path/to/my-file.txt

# It can also take stdin
cat my-file.txt | pen

The first time you run pen, it will start an emacs daemon inside a docker container. Subsequent calls to pen will use the same container and an emacsclient.

Starting the pen server with pen -nw -n starts a new pen frame with “no window” i.e. the terminal user interface, and says ‘no’ to pulling updates.

Now that it is running, you can also use the bash interop.

1
penf pf-very-witty-pick-up-lines-for-a-topic/1 slovenia

To create a shell into the docker container, simply run pen sh.

1
2
3
4
5
6
# From the host, start a shell in the docker container
pen sh

# If you want start a specific program (inside the container)
pen sh vim /
pen sh nlsh Ubuntu

Configuration

The Pen configuration consists of a config file pen.yaml and also the M-x pen-customize control panel.

1
2
3
4
# You may also access pen-customize by running
# the following on the host or container
# terminal:
pen config

Some of the values found in pen.yaml make their way into pen-customize.

I’ll explain the following example pen.yaml file.

http://github.com/semiosis/pen.el/blob/master/config/example-pen.yaml

pen.yaml lives at ~/.pen/pen.yaml on the host machine.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# When debug is on, try is disabled, and all errors throw an exception
debug: on

# Setting sh-update to on would disable caching/memoization
sh-update: off

# In future, this would disable the use of non-libre models
libre-only: off

# These variables are used by pen.el to
# automatically tailor the experience towards
# you.
fav-world-language: English
fav-programming-language: Emacs Lisp

# This overrides the language model / engine used for prompting functions
# However, if a .prompt file specifies
# force-engine, then this override will not
# override.
force-engine: OpenAI Codex

# This
force-few-completions: off

# This prevents multiple requests.
# Under normal circumstances, pen.el might
# perform multiple requests/generations to
# get to the desired quota for a prompt
# function. Under the hood, engines may have
# a max number of generations they can provide
# for a single request. force-single-
# collation ensures that only one request
# happens.
force-single-collation: off

# Force one is more extreme. It also sets
# the number of completions. So you get only
# one generation/completion per request, and only one request.
force-one: off

# This allows you to set the number of collations.
force-n-collate: ~
force-n-completions: ~
# force-temperature: ~

# Ink.el adds text properties to the emacs buffer when text has been generated.
disable-ink: off

# Disable prompt force-engine
# This can be used to truly force an engine, because individual .prompts may
# override the global force-engine custom variable with its own force-engine.
prompt-force-engine-disabled: on

# This is a heuristic used within Pen.el to make select cost-effiient options.
cost-efficient: on

# This generates alttext for the LookingGlass web browser
describe-images: on

# Default engines are used when the engine resolver (all fallbacks) fail
default-engines:
- text-to-text: OpenAI Codex
- image-to-text: AlephAlpha EUTranMM
# - text-to-image: OpenAI Dall-E
- text-to-image: ruDALL-E Malevich (XL)

# Here's a way to disable engines. This
# might be useful if you have a bad API key
# for example, and just want to disable the
# engine.
# Pattern match on the names
disabled-prompts:
# disabled-engines:
# - "AlephAlpha.*"
disabled-models:

M-SPC (aka. hyperspace) menu

If you navigate to pen-define-maps in http://github.com/semiosis/pen.el/blob/master/src/pen-example-config.el You will find the default key bindings.

A bunch of useful prompting functions for code are bound under M-SPC c, for example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
(pen-dk-easy "c l" 'pf-transpile/3)

;; The above maps the following:
(progn
  (define-key pen-map (kbd "H-TAB c l") 'pf-transpile/3)
  (define-key pen-map (kbd "H-SPC c l") 'pf-transpile/3)
  (define-key pen-map (kbd "M-Q c l") 'pf-transpile/3)
  (define-key pen-map (kbd "M-u c l") 'pf-transpile/3)
  (define-key pen-map (kbd "<H-tab> c l") 'pf-transpile/3)
  (define-key pen-map (kbd "M-SPC c l") 'pf-transpile/3)
  (define-key pen-map (kbd "M-SPC TAB c l") 'pf-transpile/3)
  (define-key pen-map (kbd "M-SPC C-M-i c l") 'pf-transpile/3)
  (define-key pen-map (kbd "H-TAB M-c M-l") 'pf-transpile/3)
  (define-key pen-map (kbd "H-SPC M-c M-l") 'pf-transpile/3)
  (define-key pen-map (kbd "M-Q M-c M-l") 'pf-transpile/3)
  (define-key pen-map (kbd "M-u M-c M-l") 'pf-transpile/3)
  (define-key pen-map (kbd "<H-tab> M-c M-l") 'pf-transpile/3)
  (define-key pen-map (kbd "M-SPC M-c M-l") 'pf-transpile/3)
  (define-key pen-map (kbd "M-SPC TAB M-c M-l") 'pf-transpile/3)
  (define-key pen-map (kbd "M-SPC C-M-i M-c M-l") 'pf-transpile/3))

The above are all valid ways to access the hyperspace menu.

I call it the hyperspace menu because one of the prefixes is H-SPC.

H- stands for the hyper key. It is invokable in pen with the chord C-M-\.

Right click menu

To invoke the right-click context menu, you may use right-click or control-left-click.

Control-Left-Click is needed for the web interface.

Use pen with the web server

There are a few differences:

  • Always runs in terminal mode.
  • Right-click doesn’t work, so use Control-Click instead for the context menu.
  • M-SPC doesn’t work, so use M-u instead as a prefix.

Use pen in gui mode

To get the GUI mode, all you need to do is run pen in a terminal somewhere.

Use pen in terminal mode

Just add -nw to one of your commands, just like running emacs.

1
2
# Example
pen -nw

Configuring pen

Firstly, there is a ~/.pen directory on your host machine.

Use lg in gui mode

There are a couple of configuration options.

  • configuration of force-images
    • This should generate missing images from alttext
  • configuration of force-text
    • This should generate missing alttext
  • Use lg in terminal mode

Do imaginary programming

The functions are built into pen.el, and so you can access them from within the pen standalone application.

Surf the imaginary web

  • Search the web by selecting

Select any text and run lg-search.

M-x lg-search

After the first generation, you may rerun a prior prompt function with the same parameters, but continue upon the a generation, such as the last partially generated web-page.

To do that run the following:

M-x pen-continue-from-hist

Now that the web page is somewhat complete, we can turn it into html.

Just select the text and run lg-render.

M-x lg-render

Now we can try imagining a random website from a URL. Just run eww with a made-up URL!

Use pen for autocompletion

Here are some basic autocompletion commands.

kb f
M-SPC 1 pen-complete-word pen-map
M-SPC 2 pen-complete-words pen-map
M-SPC 3 pen-complete-line pen-map
M-SPC 4 pen-complete-lines pen-map
M-SPC M-5 pen-complete-long pen-map

Use pen for translation

Select some text and right click, and under the menu select translate.

Sélectionnez du texte et cliquez droit, puis sous le menu cliquez sur traduire.

Run pickup lines

M-x pf-very-witty-pick-up-lines-for-a-topic/1

Translate code

M-x pf-transpile/3

Run imaginary interpreters using ii

Try it out with python first.

1
ii python

https://semiosis.github.io/ii/

Use the shell interop

1
penf -u pf-very-witty-pick-up-lines-for-a-topic/1 slovenia
I'd like to visit Slovenia with you.
1
echo Slovenia | pena -u pf-very-witty-pick-up-lines-for-a-topic/1
["I'd like to visit Slovenia with you.","I'd like to visit Slovenia.","You look like you'd be a good tourist in Slovenia.","I want to be your vacation.","I want to be your Slovenian.","I'm from Slovenia, but I'd like to visit you.","I would like to visit Slovenia one day.","I'd like to visit Slovenia and see your beautiful smile."]

Use the LSP server

Use the glossary system

Select text and press A. Select a glossary to add to.

Use cterm

Still use C-u 0 or H-u to prefix onto pen bindings to bypass/update the cache.

Use nlsh (Natural Language Shell)

You may describe the actions you wish to perform in NL, and nlsh will give you the shell commands.

nlsh expects one parameter (the operating system) and provides you with a REPL.

This allows you to rapidly create a shell that awaits your NL descriptions and translates them into real shell commands.

Talk to a chatbot

Select any text and run M-x apostrophe-start-chatbot-from-selection or M-SPC a c.

See also

Original tutorial
https://semiosis.github.io/posts/pen-tutorial/