ai/AGENTS.md
2026-03-14 16:03:53 -03:00

231 lines
6.9 KiB
Markdown

# 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))
```