# Agent Guidelines for AI (Emacs Lisp) This is an Emacs Lisp project providing dashboard packages for AI APIs. The codebase consists of: - `ai.el` - Main package that loads all dashboards - `ppq-status.el` - Dashboard for PayPerQ API (models, balance, topup) - `nano-gpt-status.el` - Dashboard for Nano-GPT API (models, usage, balance, deposit) ## Build/Load Commands ```elisp ;; Load the package in Emacs (load-file "ai.el") ;; Or load individual dashboards (load-file "ppq-status.el") (load-file "nano-gpt-status.el") ;; Byte-compile for faster loading (byte-compile-file "ai.el") (byte-compile-file "ppq-status.el") (byte-compile-file "nano-gpt-status.el") ``` ## Running Tests This project uses Emacs's built-in ERT testing framework. No test files are currently present. To run a single test in Emacs: ```elisp ;; Evaluate the test definition, then run: (ert-run-tests-interactively "^test-name$") ``` Or from command line: ```bash emacs --batch --eval "(progn (load-file \"ai.el\") (ert \"^test-name$\"))" ``` Run all tests: ```bash emacs --batch --eval "(progn (load-file \"ai.el\") (ert t))" ``` ## Linting Commands Emacs has built-in linting tools: ```elisp ;; Check documentation strings (checkdoc "ppq-status.el") ;; Check for undefined variables (elint) (elint-file "ppq-status.el") ;; Byte-compile to catch errors (byte-compile-file "ppq-status.el") ``` Run linting from command line: ```bash emacs --batch --eval "(progn (load-file \"ppq-status.el\") (checkdoc \"ppq-status.el\"))" ``` ## Code Style Guidelines ### General Principles - Use kebab-case for function and variable names (Emacs Lisp convention) - Prefix all public functions/variables with project namespace: `ppq-` or `nano-gpt-` - Private functions/variables: prefix with `--` (e.g., `ppq--internal`) - Use `lexical-binding: t` in file header ### Naming Conventions - Functions: `ppq-function-name` or `nano-gpt-function-name` - Variables: `ppq-variable-name` or `nano-gpt-variable-name` - Private functions/variables: `ppq--function-name` (double dash) - Modes: `ppq-foo-mode` - Keymaps: `ppq-foo-mode-map` - Buffer names: `*Dashboard:Name*` - Custom groups: `ppq` or `nano-gpt` ### Formatting - Indent using 2 spaces (Emacs default for Elisp) - Maximum line length: 80 characters - Use `setq` for setting variables, `defvar` for defining with docstrings - Place docstrings on line after `defun`/`defvar` opening paren - Consistent spacing around operators: `(+ x y)` not `(+x y)` ### Imports and Dependencies - Use `(require 'module)` at top of file after header - Common dependencies: `url`, `json`, `auth-source`, `org`, `tabulated-list`, `hl-line` - Use `(declare-function func "file")` for external functions (e.g., `qrencode-string`) ### Types - Use `:type` property in `defcustom` for customization types - Use type predicates: `numberp`, `stringp`, `listp`, `functionp`, `bufferp`, `vectorp` - Check for nil explicitly: `(when var ...)` not `(if var ...)` - Use `eq` for symbol comparison, `equal` for lists/strings ### Error Handling - Use `condition-case` for error catching - For async URL operations, check status with `(plist-get status :error)` - Provide meaningful error messages in docstrings - Handle `:null` values from JSON parsing explicitly ### Keymap Conventions - Use `make-sparse-keymap` for mode keymaps - Set keymap parent: `(set-keymap-parent map tabulated-list-mode-map)` - Define keys using `define-key` with string sequences (e.g., "q", "C-x C-f") - Use `setq` with `let` for local keymap creation ### Mode Definition - Use `define-derived-mode` for major modes - Set `tabulated-list-format` and `tabulated-list-entries` - Call `tabulated-list-init-header` and `tabulated-list-print` - Set `inhibit-read-only` before modifying read-only buffers - Enable `hl-line-mode` for highlighting current line ### Async Operations - Use `url-retrieve` for async HTTP requests - Define callback functions with `status` parameter - Check for errors: `(unless (plist-get status :error) ...)` - Use `(goto-char (point-min))` before parsing buffer content - Use `(re-search-forward "^$" nil t)` to skip HTTP headers ### Documentation - Every `defun` and `defvar` must have a docstring - Docstrings start with descriptive verb ("Show", "Fetch", "Copy") - Document interactive commands with `(interactive)` and "Calling from program..." - Use `;;;###autoload` for user-facing commands ### Package Management - Use `defcustom` for user-configurable variables with `:type`, `:group`, `:default` - Use `defvar` for internal variables with docstrings - Prefix custom group with project namespace ## Project-Specific Patterns ### Safe List Access (ALIST) ```elisp (defun ppq--aget (alist key) "Safely get value from ALIST for KEY, returns nil if null." (let ((val (cdr (assq key alist)))) (unless (eq val :null) val))) ``` ### Buffer Management ```elisp ;; Get or create buffer (get-buffer-create "*Buffer-Name*") ;; Modify buffer safely (with-current-buffer buf ...) ;; For read-only buffers (let ((inhibit-read-only t)) (erase-buffer) ...) ``` ### Window Configuration ```elisp ;; Save/restore window config (setq ppq--window-config (current-window-configuration)) (set-window-configuration ppq--window-config) ;; Split windows (split-window-right) ; vertical split (split-window-below) ; horizontal split (delete-other-windows) ; maximize current window ``` ### JSON Parsing ```elisp ;; Parse JSON with alist object type (let* ((json (json-parse-string (buffer-substring (point) (point-max)) :object-type 'alist)) (data (ppq--aget json 'data))) ...) ``` ### Tabulated List Mode ```elisp ;; Format specification: [("Column" width sort-fn) ...] (setq tabulated-list-format [("Name" 30 t) ("ID" 40 t) ("Created" 18 t) ("Ctx" 8 t :right-align t :pad-right 2)]) ;; Entries: (id [col1 col2 col3] ...) (setq tabulated-list-entries (mapcar (lambda (item) (list (get-id item) (vector (get-name item) (get-id item) ...))) data)) ``` ### Properize Text with Faces ```elisp (propertize text 'font-lock-face face) ;; face can be: '(:foreground "green"), 'bold, nil, etc. ``` ### Auth-Source Integration ```elisp (defun ppq--get-api-key () "Get API key from auth-source for ppq.ai." (let ((secret (auth-source-search :host "ppq.ai" :secret t))) (when secret (funcall (plist-get (car secret) :secret))))) ``` ### URL Request Patterns ```elisp ;; GET request (url-retrieve url #'callback nil t t) ;; POST with JSON body (let ((url-request-method "POST") (url-request-extra-headers '(("Content-Type" . "application/json"))) (url-request-data (json-encode data))) (url-retrieve url #'callback nil nil t)) ;; POST with auth header (let ((url-request-method "POST") (url-request-extra-headers (list (cons "Authorization" (format "Bearer %s" key)))) (url-request-data (json-encode data))) (url-retrieve url #'callback nil nil t)) ```