rename: ollie-* symbols to ellie-* throughout ellie.el

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-04-18 13:36:12 +02:00
parent 18e94c7de0
commit ed3df6c7b3
2 changed files with 195 additions and 195 deletions

View File

@ -45,13 +45,13 @@ Optionally set a mount path and default session options:
| Command | Description |
|---------|-------------|
| `M-x ollie` | Open chat; attach to last session for `default-directory`, or create a new one |
| `M-x ollie-new-and-open` | Force-create a new session and open chat |
| `M-x ollie-attach-and-open` | Attach to an existing session by ID |
| `M-x ellie` | Open chat; attach to last session for `default-directory`, or create a new one |
| `M-x ellie-new-and-open` | Force-create a new session and open chat |
| `M-x ellie-attach-and-open` | Attach to an existing session by ID |
## UI
In the `*ollie*` buffer:
In the `*ellie*` buffer:
| Key | Action |
|-----|--------|
@ -65,7 +65,7 @@ In the `*ollie*` buffer:
| `C-c C-d` | Open the ollie mount in Dired |
| `g` | Force-refresh the chat log |
In the `*ollie-input*` window:
In the `*ellie-input*` window:
| Key | Action |
|-----|--------|

380
ellie.el
View File

@ -12,9 +12,9 @@
;; Quick start:
;;
;; (require 'ellie) ; or use use-package / :load-path
;; M-x ollie ; opens *ollie*, attaches to last session or creates one
;; M-x ellie ; opens *ellie*, attaches to last session or creates one
;;
;; In the *ollie* buffer:
;; In the *ellie* buffer:
;;
;; RET open an input window to compose and submit a prompt
;; C-c C-q open an input window to compose a queued prompt
@ -25,7 +25,7 @@
;; C-c C-p show the rendered system prompt
;; g force-refresh the chat log
;;
;; In the *ollie-input* window:
;; In the *ellie-input* window:
;;
;; C-c C-c submit
;; C-c C-k cancel
@ -44,74 +44,74 @@
;;;; ──────────────── Customization ────────────────
(defgroup ollie nil
(defgroup ellie nil
"Emacs client for ollie-9p."
:group 'tools
:prefix "ollie-")
:prefix "ellie-")
(defcustom ollie-mount-directory
(defcustom ellie-mount-directory
(or (getenv "OLLIE") (expand-file-name "~/mnt/ollie"))
"Mount point for the ollie-9p filesystem."
:type 'directory
:group 'ollie)
:group 'ellie)
(defcustom ollie-default-session-opts nil
(defcustom ellie-default-session-opts nil
"Default options passed when creating a new session.
An alist of (KEY . VALUE) string pairs; valid keys are
\"backend\", \"model\", \"agent\", and \"cwd\"."
:type '(alist :key-type string :value-type string)
:group 'ollie)
:group 'ellie)
(defcustom ollie-poll-interval 0.5
(defcustom ellie-poll-interval 0.5
"Seconds between chat-file polls (used when file-notify is unavailable)."
:type 'number
:group 'ollie)
:group 'ellie)
(defcustom ollie-input-window-height 8
(defcustom ellie-input-window-height 8
"Height in lines of the prompt input window."
:type 'integer
:group 'ollie)
:group 'ellie)
;;;; ──────────────── Internal state ────────────────
(defvar ollie--session-id nil
(defvar ellie--session-id nil
"ID of the currently active session (global).")
(defvar ollie--chat-size -1
(defvar ellie--chat-size -1
"Last known byte count of the chat file; -1 means unread.")
(defvar ollie--poll-timer nil
(defvar ellie--poll-timer nil
"Timer for periodic refresh.")
(defconst ollie--buf "*ollie*" "Name of the chat display buffer.")
(defconst ollie--ibuf "*ollie-input*" "Name of the prompt input buffer.")
(defconst ellie--buf "*ellie*" "Name of the chat display buffer.")
(defconst ellie--ibuf "*ellie-input*" "Name of the prompt input buffer.")
;;;; ──────────────── Paths ────────────────
(defun ollie--sessions-dir ()
(expand-file-name "s" ollie-mount-directory))
(defun ellie--sessions-dir ()
(expand-file-name "s" ellie-mount-directory))
(defun ollie--sessions-new-path ()
(expand-file-name "new" (ollie--sessions-dir)))
(defun ellie--sessions-new-path ()
(expand-file-name "new" (ellie--sessions-dir)))
(defun ollie--session-file (name)
(defun ellie--session-file (name)
"Return the full path to file NAME inside the current session directory.
Signals `user-error' if no session is active."
(unless ollie--session-id
(unless ellie--session-id
(user-error "No active ollie session"))
(expand-file-name name
(expand-file-name ollie--session-id (ollie--sessions-dir))))
(expand-file-name ellie--session-id (ellie--sessions-dir))))
(defun ollie--last-session-path ()
(defun ellie--last-session-path ()
(expand-file-name "~/.config/ollie/last-session"))
;;;; ──────────────── Low-level file helpers ────────────────
(defun ollie--fwrite (path text)
(defun ellie--fwrite (path text)
"Write TEXT to PATH, suppressing the minibuffer message."
(write-region text nil path nil 'silent))
(defun ollie--fread (path)
(defun ellie--fread (path)
"Return the contents of PATH as a string, or nil if it does not exist."
(when (file-exists-p path)
(with-temp-buffer
@ -121,9 +121,9 @@ Signals `user-error' if no session is active."
;;;; ──────────────── Session enumeration ────────────────
(defun ollie--session-ids ()
(defun ellie--session-ids ()
"Return a list of all current session IDs."
(let ((sdir (ollie--sessions-dir)))
(let ((sdir (ellie--sessions-dir)))
(when (file-directory-p sdir)
(seq-filter
(lambda (name) (not (string-prefix-p "." name)))
@ -133,11 +133,11 @@ Signals `user-error' if no session is active."
;;;; ──────────────── Last-session persistence ────────────────
(defun ollie--persist-session (id)
(defun ellie--persist-session (id)
"Upsert the {cwd}TAB{id} entry for `default-directory' in the last-session file."
(let* ((path (ollie--last-session-path))
(let* ((path (ellie--last-session-path))
(cwd (expand-file-name default-directory))
(raw (ollie--fread path))
(raw (ellie--fread path))
(lines (when raw
(seq-filter (lambda (l) (not (string-empty-p (string-trim l))))
(split-string raw "\n"))))
@ -151,17 +151,17 @@ Signals `user-error' if no session is active."
l)))
lines)))
(make-directory (file-name-directory path) t)
(ollie--fwrite path
(ellie--fwrite path
(concat (mapconcat #'identity
(if found new-lines (append new-lines (list entry)))
"\n")
"\n"))))
(defun ollie--recall-session ()
(defun ellie--recall-session ()
"Return the last-used session ID for `default-directory' if it still exists, else nil."
(let* ((path (ollie--last-session-path))
(let* ((path (ellie--last-session-path))
(cwd (expand-file-name default-directory))
(raw (ollie--fread path)))
(raw (ellie--fread path)))
(when raw
(catch 'found
(dolist (line (split-string raw "\n"))
@ -170,66 +170,66 @@ Signals `user-error' if no session is active."
(string= (car parts) cwd))
(let ((id (string-trim (cadr parts))))
(when (and (not (string-empty-p id))
(file-directory-p (expand-file-name id (ollie--sessions-dir))))
(file-directory-p (expand-file-name id (ellie--sessions-dir))))
(throw 'found id))))))))))
;;;; ──────────────── Session lifecycle ────────────────
(defun ollie-new-session (&optional opts)
(defun ellie-new-session (&optional opts)
"Create a new ollie session and make it current.
OPTS is an alist of option pairs; defaults to `ollie-default-session-opts'.
OPTS is an alist of option pairs; defaults to `ellie-default-session-opts'.
Returns the new session ID."
(interactive)
(unless (file-directory-p ollie-mount-directory)
(user-error "ollie-9p not mounted at %s (customize `ollie-mount-directory')"
ollie-mount-directory))
(let* ((effective-opts (or opts ollie-default-session-opts))
(unless (file-directory-p ellie-mount-directory)
(user-error "ollie-9p not mounted at %s (customize `ellie-mount-directory')"
ellie-mount-directory))
(let* ((effective-opts (or opts ellie-default-session-opts))
;; cwd is required; inject it if not already present.
(effective-opts (if (assoc "cwd" effective-opts)
effective-opts
(cons (cons "cwd" default-directory) effective-opts)))
(before (ollie--session-ids))
(before (ellie--session-ids))
(seen (make-hash-table :test 'equal))
(cmd (mapconcat (lambda (kv)
(format "%s=%s" (car kv) (cdr kv)))
effective-opts " ")))
(dolist (id before) (puthash id t seen))
(ollie--fwrite (ollie--sessions-new-path) (concat cmd "\n"))
(ellie--fwrite (ellie--sessions-new-path) (concat cmd "\n"))
(let ((deadline (+ (float-time) 5.0))
result)
(while (and (not result) (< (float-time) deadline))
(dolist (id (ollie--session-ids))
(dolist (id (ellie--session-ids))
(unless (gethash id seen) (setq result id)))
(unless result (sleep-for 0.05)))
(unless result
(user-error "Timed out waiting for a new ollie session"))
(setq ollie--session-id result)
(ollie--persist-session result)
(setq ellie--session-id result)
(ellie--persist-session result)
(message "Created ollie session %s" result)
result)))
(defun ollie-attach (id)
(defun ellie-attach (id)
"Attach to an existing session by ID and make it current."
(interactive
(let ((ids (ollie--session-ids)))
(let ((ids (ellie--session-ids)))
(unless ids (user-error "No ollie sessions available"))
(list (completing-read "Attach to session: " ids nil t))))
(unless (file-directory-p (expand-file-name id (ollie--sessions-dir)))
(unless (file-directory-p (expand-file-name id (ellie--sessions-dir)))
(user-error "Session not found: %s" id))
(setq ollie--session-id id)
(ollie--persist-session id)
(setq ellie--session-id id)
(ellie--persist-session id)
(message "Attached to ollie session %s" id))
(defun ollie-kill-session ()
(defun ellie-kill-session ()
"Kill the current ollie session."
(interactive)
(unless ollie--session-id (user-error "No active session"))
(when (yes-or-no-p (format "Kill ollie session %s? " ollie--session-id))
(delete-directory (expand-file-name ollie--session-id (ollie--sessions-dir)) t)
(ollie--stop-watching)
(setq ollie--session-id nil
ollie--chat-size -1)
(when-let (buf (get-buffer ollie--buf))
(unless ellie--session-id (user-error "No active session"))
(when (yes-or-no-p (format "Kill ollie session %s? " ellie--session-id))
(delete-directory (expand-file-name ellie--session-id (ellie--sessions-dir)) t)
(ellie--stop-watching)
(setq ellie--session-id nil
ellie--chat-size -1)
(when-let (buf (get-buffer ellie--buf))
(with-current-buffer buf
(let ((inhibit-read-only t))
(erase-buffer)
@ -237,87 +237,87 @@ Returns the new session ID."
(insert "Press 'n' or C-c C-n to start a new session.\n"))))
(message "Session killed")))
(defun ollie-rename-session (new-name)
(defun ellie-rename-session (new-name)
"Rename the current ollie session to NEW-NAME."
(interactive "sNew session name: ")
(unless ollie--session-id (user-error "No active session"))
(unless ellie--session-id (user-error "No active session"))
(when (string-empty-p (string-trim new-name))
(user-error "Empty name"))
(let ((old-dir (expand-file-name ollie--session-id (ollie--sessions-dir)))
(new-dir (expand-file-name new-name (ollie--sessions-dir))))
(let ((old-dir (expand-file-name ellie--session-id (ellie--sessions-dir)))
(new-dir (expand-file-name new-name (ellie--sessions-dir))))
(rename-file old-dir new-dir)
(setq ollie--session-id new-name)
(ollie--persist-session new-name)
(setq ellie--session-id new-name)
(ellie--persist-session new-name)
(force-mode-line-update)
(message "Renamed session to %s" new-name)))
;;;; ──────────────── State ────────────────
(defun ollie--state ()
(defun ellie--state ()
"Return the current session state string, or \"unknown\" on any error."
(condition-case nil
(or (when-let (s (ollie--fread (ollie--session-file "state")))
(or (when-let (s (ellie--fread (ellie--session-file "state")))
(string-trim s))
"unknown")
(error "unknown")))
(defun ollie--idle-p ()
(defun ellie--idle-p ()
"Return non-nil if the session is currently idle."
(ignore-errors (string= (ollie--state) "idle")))
(ignore-errors (string= (ellie--state) "idle")))
;;;; ──────────────── Agent operations ────────────────
(defun ollie-submit (text)
(defun ellie-submit (text)
"Submit TEXT as a prompt to the current session.
The server dispatches it asynchronously."
(when (string-empty-p (string-trim text))
(user-error "Empty prompt"))
(ollie--fwrite (ollie--session-file "prompt") text))
(ellie--fwrite (ellie--session-file "prompt") text))
(defun ollie-queue (text)
(defun ellie-queue (text)
"Enqueue TEXT as a prompt to run after the current turn finishes."
(when (string-empty-p (string-trim text))
(user-error "Empty prompt"))
(ollie--fwrite (ollie--session-file "enqueue") text))
(ellie--fwrite (ellie--session-file "enqueue") text))
(defun ollie-stop ()
(defun ellie-stop ()
"Send a stop signal to the current turn."
(interactive)
(ollie--fwrite (ollie--session-file "ctl") "stop\n")
(ellie--fwrite (ellie--session-file "ctl") "stop\n")
(message "ollie: stopped"))
;;;; ──────────────── Diff faces ────────────────
(defface ollie-diff-added
(defface ellie-diff-added
'((((background dark)) :foreground "#5faf5f")
(((background light)) :foreground "#3a7a3a"))
"Face for added lines in tool-result diff output."
:group 'ollie)
:group 'ellie)
(defface ollie-diff-removed
(defface ellie-diff-removed
'((((background dark)) :foreground "#d75f5f")
(((background light)) :foreground "#a02020"))
"Face for removed lines in tool-result diff output."
:group 'ollie)
:group 'ellie)
(defface ollie-diff-hunk-header
(defface ellie-diff-hunk-header
'((((background dark)) :foreground "#5fafaf")
(((background light)) :foreground "#1a6a6a"))
"Face for hunk headers in tool-result diff output."
:group 'ollie)
:group 'ellie)
;;;; ──────────────── Chat display ────────────────
;; We cache the mtime so we only re-read the file when it actually changes.
;; The mode-line state is updated on every refresh tick.
(defvar-local ollie--mode-line-state "unknown"
(defvar-local ellie--mode-line-state "unknown"
"Cached session state string for the mode line.")
(defvar-local ollie--mode-line-usage ""
(defvar-local ellie--mode-line-usage ""
"Cached usage string for the mode line.")
(defun ollie--block-start-p (line)
(defun ellie--block-start-p (line)
"Return non-nil if LINE begins a named chat block."
(or (string-prefix-p "-> " line)
(string-prefix-p "user: " line)
@ -326,7 +326,7 @@ The server dispatches it asynchronously."
(string-prefix-p "error: " line)
(string-prefix-p "agent stalled" line)))
(defun ollie--colorize-diffs ()
(defun ellie--colorize-diffs ()
"Add diff highlighting to tool-result blocks in the current buffer.
Must be called after `ansi-color-apply-on-region'."
(save-excursion
@ -341,195 +341,195 @@ Must be called after `ansi-color-apply-on-region'."
((string-prefix-p "-> " line)
(setq in-result t))
;; Block-start: exit result mode; don't colorize this line.
((and in-result (ollie--block-start-p line))
((and in-result (ellie--block-start-p line))
(setq in-result nil))
;; Inside a result block: colorize diff lines.
(in-result
(let ((face (cond
((or (string-prefix-p "+++" line)
(string-prefix-p "−--" line)) 'shadow)
((string-prefix-p "+" line) 'ollie-diff-added)
((string-prefix-p "−" line) 'ollie-diff-removed)
((string-prefix-p "@@" line) 'ollie-diff-hunk-header))))
((string-prefix-p "+" line) 'ellie-diff-added)
((string-prefix-p "−" line) 'ellie-diff-removed)
((string-prefix-p "@@" line) 'ellie-diff-hunk-header))))
(when face
(put-text-property bol eol 'face face)))))
(forward-line 1))))))
(defun ollie--refresh ()
(defun ellie--refresh ()
"Refresh the chat buffer if the chat file has grown on disk.
Also updates the cached mode-line state."
(let* ((buf (get-buffer ollie--buf))
(chat (condition-case nil (ollie--session-file "chat") (error nil)))
(let* ((buf (get-buffer ellie--buf))
(chat (condition-case nil (ellie--session-file "chat") (error nil)))
(attrs (and chat (file-attributes chat)))
(size (and attrs (file-attribute-size attrs))))
(when buf
(with-current-buffer buf
(setq ollie--mode-line-state (ollie--state))
(setq ollie--mode-line-usage
(setq ellie--mode-line-state (ellie--state))
(setq ellie--mode-line-usage
(or (condition-case nil
(when-let (s (ollie--fread (ollie--session-file "ctxsz")))
(when-let (s (ellie--fread (ellie--session-file "ctxsz")))
(string-trim s))
(error nil))
""))
;; The chat log is append-only, so size increasing means new content.
(when (and size (> size ollie--chat-size))
(setq ollie--chat-size size)
(when (and size (> size ellie--chat-size))
(setq ellie--chat-size size)
(let ((inhibit-read-only t)
(at-end (= (point) (point-max))))
(erase-buffer)
(insert-file-contents chat)
(ansi-color-apply-on-region (point-min) (point-max))
(ollie--colorize-diffs)
(ellie--colorize-diffs)
(when at-end (goto-char (point-max)))))
(force-mode-line-update)))))
;;;; ──────────────── Polling ────────────────
;; inotify does not fire on 9P mounts, so we always use a timer.
;; The mtime check in `ollie--refresh' keeps this cheap.
;; The mtime check in `ellie--refresh' keeps this cheap.
(defun ollie--start-watching ()
(defun ellie--start-watching ()
"Start a periodic timer to tail the chat file."
(ollie--stop-watching)
(setq ollie--poll-timer
(run-with-timer ollie-poll-interval ollie-poll-interval #'ollie--refresh)))
(ellie--stop-watching)
(setq ellie--poll-timer
(run-with-timer ellie-poll-interval ellie-poll-interval #'ellie--refresh)))
(defun ollie--stop-watching ()
(defun ellie--stop-watching ()
"Cancel the refresh timer."
(when ollie--poll-timer
(cancel-timer ollie--poll-timer)
(setq ollie--poll-timer nil)))
(when ellie--poll-timer
(cancel-timer ellie--poll-timer)
(setq ellie--poll-timer nil)))
;;;; ──────────────── Input buffer ────────────────
(defvar-local ollie--input-queued nil
(defvar-local ellie--input-queued nil
"Non-nil when this input buffer will enqueue rather than submit.")
(defvar ollie-input-mode-map
(defvar ellie-input-mode-map
(let ((m (make-sparse-keymap)))
(define-key m (kbd "C-c C-c") #'ollie-input-submit)
(define-key m (kbd "C-c C-k") #'ollie-input-cancel)
(define-key m (kbd "C-c C-c") #'ellie-input-submit)
(define-key m (kbd "C-c C-k") #'ellie-input-cancel)
m)
"Keymap for `ollie-input-mode'.")
"Keymap for `ellie-input-mode'.")
(define-derived-mode ollie-input-mode text-mode "Ollie-Input"
(define-derived-mode ellie-input-mode text-mode "Ollie-Input"
"Major mode for composing ollie prompts.
\\{ollie-input-mode-map}"
\\{ellie-input-mode-map}"
(setq-local header-line-format
'(:eval
(format " %s · C-c C-c submit · C-c C-k cancel"
(if ollie--input-queued
(if ellie--input-queued
(propertize "Queue prompt" 'face 'warning)
(propertize "Send prompt" 'face 'success))))))
(defun ollie--open-input (&optional queued)
(defun ellie--open-input (&optional queued)
"Open the prompt input window below the current window.
QUEUED non-nil means the prompt will be enqueued, not submitted immediately."
(let ((buf (get-buffer-create ollie--ibuf)))
(let ((buf (get-buffer-create ellie--ibuf)))
(with-current-buffer buf
(erase-buffer)
(ollie-input-mode)
(setq ollie--input-queued queued))
(ellie-input-mode)
(setq ellie--input-queued queued))
(display-buffer buf
`(display-buffer-below-selected
(window-height . ,ollie-input-window-height)))
(window-height . ,ellie-input-window-height)))
(when-let (win (get-buffer-window buf t))
(select-window win))))
(defun ollie-input-submit ()
(defun ellie-input-submit ()
"Submit the contents of the input buffer as a prompt."
(interactive)
(let ((text (buffer-string))
(queued ollie--input-queued))
(ollie--close-input-window)
(queued ellie--input-queued))
(ellie--close-input-window)
(if queued
(ollie-queue text)
(ollie-submit text))))
(ellie-queue text)
(ellie-submit text))))
(defun ollie-input-cancel ()
(defun ellie-input-cancel ()
"Discard the input and close the input window."
(interactive)
(ollie--close-input-window))
(ellie--close-input-window))
(defun ollie--close-input-window ()
(defun ellie--close-input-window ()
"Kill the input buffer and delete its window."
(when-let (win (get-buffer-window ollie--ibuf t))
(when-let (win (get-buffer-window ellie--ibuf t))
(delete-window win))
(when-let (buf (get-buffer ollie--ibuf))
(when-let (buf (get-buffer ellie--ibuf))
(kill-buffer buf)))
;;;; ──────────────── Chat major mode ────────────────
(defvar ollie-mode-map
(defvar ellie-mode-map
(let ((m (make-sparse-keymap)))
(define-key m (kbd "RET") #'ollie-send-input)
(define-key m (kbd "C-c C-q") #'ollie-queue-input)
(define-key m (kbd "C-c C-c") #'ollie-stop)
(define-key m (kbd "C-c C-k") #'ollie-kill-session)
(define-key m (kbd "C-c C-r") #'ollie-rename-session)
(define-key m (kbd "C-c C-n") #'ollie-new-and-open)
(define-key m (kbd "C-c C-a") #'ollie-attach-and-open)
(define-key m (kbd "C-c C-d") #'ollie-dired)
(define-key m (kbd "C-c C-p") #'ollie-show-system-prompt)
(define-key m (kbd "n") #'ollie-new-and-open)
(define-key m (kbd "g") #'ollie-refresh)
(define-key m (kbd "RET") #'ellie-send-input)
(define-key m (kbd "C-c C-q") #'ellie-queue-input)
(define-key m (kbd "C-c C-c") #'ellie-stop)
(define-key m (kbd "C-c C-k") #'ellie-kill-session)
(define-key m (kbd "C-c C-r") #'ellie-rename-session)
(define-key m (kbd "C-c C-n") #'ellie-new-and-open)
(define-key m (kbd "C-c C-a") #'ellie-attach-and-open)
(define-key m (kbd "C-c C-d") #'ellie-dired)
(define-key m (kbd "C-c C-p") #'ellie-show-system-prompt)
(define-key m (kbd "n") #'ellie-new-and-open)
(define-key m (kbd "g") #'ellie-refresh)
m)
"Keymap for `ollie-mode'.")
"Keymap for `ellie-mode'.")
(defun ollie-send-input ()
(defun ellie-send-input ()
"Open the input window to compose and submit a prompt."
(interactive)
(ollie--open-input nil))
(ellie--open-input nil))
(defun ollie-queue-input ()
(defun ellie-queue-input ()
"Open the input window to compose a queued prompt."
(interactive)
(ollie--open-input t))
(ellie--open-input t))
(defun ollie-refresh ()
(defun ellie-refresh ()
"Force a refresh of the chat log from disk."
(interactive)
(setq ollie--chat-size -1)
(ollie--refresh))
(setq ellie--chat-size -1)
(ellie--refresh))
(defun ollie-dired ()
(defun ellie-dired ()
"Open the ollie-9p mount point in Dired."
(interactive)
(dired ollie-mount-directory))
(dired ellie-mount-directory))
(define-derived-mode ollie-mode special-mode "Ollie"
(define-derived-mode ellie-mode special-mode "Ollie"
"Major mode for the ollie chat interface.
Key bindings:
\\{ollie-mode-map}"
:group 'ollie
\\{ellie-mode-map}"
:group 'ellie
(buffer-disable-undo)
(setq-local mode-line-format
`("%e"
mode-line-front-space
(:eval
(propertize (format " ollie:%s " (or ollie--session-id "-"))
(propertize (format " ellie:%s" (or ellie--session-id "-"))
'face 'mode-line-buffer-id))
(:eval
(if (string= ollie--mode-line-state "idle")
(if (string= ellie--mode-line-state "idle")
(propertize "idle" 'face 'success)
(propertize ollie--mode-line-state 'face 'warning)))
(propertize ellie--mode-line-state 'face 'warning)))
(:eval
(when (not (string-empty-p ollie--mode-line-usage))
(concat " " (replace-regexp-in-string "%" "%%" ollie--mode-line-usage))))
(when (not (string-empty-p ellie--mode-line-usage))
(concat " " (replace-regexp-in-string "%" "%%" ellie--mode-line-usage))))
" "
mode-line-end-spaces)))
;;;; ──────────────── System prompt ────────────────
(defun ollie-show-system-prompt ()
(defun ellie-show-system-prompt ()
"Display the rendered system prompt for the current session in a buffer."
(interactive)
(let* ((path (ollie--session-file "systemprompt"))
(text (ollie--fread path)))
(let* ((path (ellie--session-file "systemprompt"))
(text (ellie--fread path)))
(unless text
(user-error "Could not read system prompt"))
(let ((buf (get-buffer-create "*ollie-system-prompt*")))
(let ((buf (get-buffer-create "*ellie-system-prompt*")))
(with-current-buffer buf
(let ((inhibit-read-only t))
(erase-buffer)
@ -543,47 +543,47 @@ Key bindings:
;;;; ──────────────── Entry points ────────────────
;;;###autoload
(defun ollie ()
(defun ellie ()
"Open the ollie chat interface.
Attaches to the last session if it still exists; otherwise creates a new one."
(interactive)
(unless (file-directory-p ollie-mount-directory)
(user-error "ollie-9p not mounted at %s (customize `ollie-mount-directory')"
ollie-mount-directory))
(unless ollie--session-id
(if-let (last (ollie--recall-session))
(unless (file-directory-p ellie-mount-directory)
(user-error "ollie-9p not mounted at %s (customize `ellie-mount-directory')"
ellie-mount-directory))
(unless ellie--session-id
(if-let (last (ellie--recall-session))
(progn
(setq ollie--session-id last)
(setq ellie--session-id last)
(message "Attached to last ollie session %s" last))
(ollie-new-session)))
(ollie--open-buf))
(ellie-new-session)))
(ellie--open-buf))
;;;###autoload
(defun ollie-new-and-open ()
(defun ellie-new-and-open ()
"Create a new ollie session and open the chat buffer."
(interactive)
(ollie-new-session)
(ollie--open-buf))
(ellie-new-session)
(ellie--open-buf))
;;;###autoload
(defun ollie-attach-and-open (id)
(defun ellie-attach-and-open (id)
"Attach to session ID and open the chat buffer."
(interactive
(let ((ids (ollie--session-ids)))
(let ((ids (ellie--session-ids)))
(unless ids (user-error "No ollie sessions available"))
(list (completing-read "Session: " ids nil t))))
(ollie-attach id)
(ollie--open-buf))
(ellie-attach id)
(ellie--open-buf))
(defun ollie--open-buf ()
(defun ellie--open-buf ()
"Open or switch to the *ollie* chat buffer."
(let ((buf (get-buffer-create ollie--buf)))
(let ((buf (get-buffer-create ellie--buf)))
(with-current-buffer buf
(unless (derived-mode-p 'ollie-mode)
(ollie-mode))
(setq ollie--chat-size -1)
(ollie--refresh)
(ollie--start-watching))
(unless (derived-mode-p 'ellie-mode)
(ellie-mode))
(setq ellie--chat-size -1)
(ellie--refresh)
(ellie--start-watching))
(pop-to-buffer buf)))
(provide 'ellie)