TUI config panel: full implementation, working through tmux

- Package: passepartout.gateway-tui (uses croatoan, usocket, bordeaux-threads)
- Config panel with 4 sections: Providers, Cascade, Models, View
- Config-render functions for each section with live provider data
- Fixed add-string keyword argument order (was positional)
- Added function-keys-enabled-p for arrow key handling
- Fixed config-render-models balance (missing close paren)
- Fixed config-render balance (missing close paren)
- Added providers-configured-p to core-loop
- First-run welcome messages when no providers configured
- Daemon-side: WELCOME log on empty *probabilistic-backends*
Known: F2 function key needs terminal-level keypad mode; /config typed command works
This commit is contained in:
2026-05-04 11:09:22 -04:00
parent 3bb797ab9e
commit 31e53e675e
3 changed files with 104 additions and 85 deletions

View File

@@ -1,4 +1,7 @@
(in-package :passepartout) (defpackage :passepartout.gateway-tui
(:use :cl :croatoan :passepartout :usocket :bordeaux-threads)
(:export :tui-main))
(in-package :passepartout.gateway-tui)
(defvar *stream* nil "TCP stream to daemon") (defvar *stream* nil "TCP stream to daemon")
(defvar *input-buffer* nil "Current input line as reversed char list") (defvar *input-buffer* nil "Current input line as reversed char list")
@@ -100,12 +103,12 @@
(w (or (width win) 78))) (w (or (width win) 78)))
(clear win) (clear win)
(box win 0 0) (box win 0 0)
(add-string win 1 1 (format nil " Passepartout ~a [~a] msgs:~a scroll:~a" (add-string win (format nil " Passepartout ~a [~a] msgs:~a scroll:~a"
(if *stream* "● Connected" "○ Disconnected") (if *stream* "● Connected" "○ Disconnected")
(ecase *tui-mode* (:chat "CHAT") (:config "CONFIG")) (ecase *tui-mode* (:chat "CHAT") (:config "CONFIG"))
(length *chat-history*) (length *chat-history*)
(if (> *chat-scroll-pos* 0) (format nil "~a↑" *chat-scroll-pos*) "0"))) (if (> *chat-scroll-pos* 0) (format nil "~a↑" *chat-scroll-pos*) "0")) :y 1 :x 1)
(add-string win 2 1 (format nil " ~a" (timestamp))) (add-string win (format nil " ~a" (timestamp)) :y 2 :x 1)
(refresh win))) (refresh win)))
(defun chat-render (win h) (defun chat-render (win h)
@@ -123,14 +126,14 @@
(text (cdr entry)) (text (cdr entry))
(prefix (if (eq dir :sent) "⬆" "⬇")) (prefix (if (eq dir :sent) "⬆" "⬇"))
(label (format nil "~a [~a] ~a" prefix (timestamp) text))) (label (format nil "~a [~a] ~a" prefix (timestamp) text)))
(add-string win 1 y label) (add-string win label :y 1 :x y)
(incf y)))))) (incf y))))))
(refresh win)) (refresh win))
(defun input-render (win) (defun input-render (win)
"Draw the input line." "Draw the input line."
(clear win) (clear win)
(add-string win 0 0 (input-text)) (add-string win (input-text) :y 0 :x 0)
(refresh win)) (refresh win))
(defun config-provider-line (provider) (defun config-provider-line (provider)
@@ -142,39 +145,39 @@
(let ((w (or (width win) 78))) (let ((w (or (width win) 78)))
(clear win) (clear win)
(box win 0 0) (box win 0 0)
(add-string win 1 1 "[1] Providers [2] Cascade [3] Models [4] View [q] Back") (add-string win "[1] Providers [2] Cascade [3] Models [4] View [q] Back" :y 1 :x 1)
(add-string win 2 1 (format nil " Set provider: ~a" (add-string win (format nil " Set provider: ~a"
(mapcar #'config-provider-line *provider-cascade*))) (mapcar #'config-provider-line *provider-cascade*)) :y 2 :x 1)
;; Show unconfigured but available providers on line 3-8 ;; Show unconfigured but available providers on line 3-8
(let ((y 3) (let ((y 3)
(unconf (remove-if (lambda (p) (provider-available-p p)) (unconf (remove-if (lambda (p) (provider-available-p p))
(mapcar #'car *provider-configs*))) (mapcar #'car *provider-configs*)))
(conf (count-if #'provider-available-p (mapcar #'car *provider-configs*)))) (conf (count-if #'provider-available-p (mapcar #'car *provider-configs*))))
(add-string win 1 (- w 14) (format nil "~a/~a active" conf (length *provider-configs*))) (add-string win (format nil "~a/~a active" conf (length *provider-configs*)) :y 1 :x (- w 14))
(when (zerop conf) (when (zerop conf)
(add-string win y 1 "** No providers configured. Press 1 to set up.") (add-string win "** No providers configured. Press 1 to set up." :y y :x 1)
(incf y)) (incf y))
(when unconf (when unconf
(add-string win y 1 (format nil "Available: ~{~a~^, ~}" (add-string win (format nil "Available: ~{~a~^, ~}"
(mapcar (lambda (k) (string-downcase (string k))) unconf))) (mapcar (lambda (k) (string-downcase (string k))) unconf)) :y y :x 1)
(incf y)) (incf y))
(add-string win y 1 (format nil "Free tier: openrouter (openrouter.ai), gemma-4 from google")) (add-string win (format nil "Free tier: openrouter (openrouter.ai), gemma-4 from google") :y y :x 1)
(refresh win))) (refresh win))))
(defun config-render-providers (win) (defun config-render-providers (win)
"Show all providers with availability status." "Show all providers with availability status."
(let ((w (or (width win) 78))) (let ((w (or (width win) 78)))
(clear win) (clear win)
(box win 0 0) (box win 0 0)
(add-string win 1 1 "[1] Providers [2] Cascade [3] Models [4] View [q] Back") (add-string win "[1] Providers [2] Cascade [3] Models [4] View [q] Back" :y 1 :x 1)
(add-string win 2 1 " Provider Status Key Env") (add-string win " Provider Status Key Env" :y 2 :x 1)
(loop for entry in *provider-configs* (loop for entry in *provider-configs*
for i from 3 for i from 3
do (let* ((provider (car entry)) do (let* ((provider (car entry))
(config (cdr entry)) (config (cdr entry))
(avail (provider-available-p provider)) (avail (provider-available-p provider))
(key-env (or (getf config :key-env) (getf config :url-env) "--"))) (key-env (or (getf config :key-env) (getf config :url-env) "--")))
(add-string win i 1 (format nil " ~:[ ✗~; ✓~] ~20@(~a~) ~a" avail provider key-env)))) (add-string win (format nil " ~:[ ✗~; ✓~] ~20a ~a" avail provider key-env) :y i :x 1))
(refresh win))) (refresh win)))
(defun config-render-cascade (win) (defun config-render-cascade (win)
@@ -182,10 +185,10 @@
(let ((w (or (width win) 78))) (let ((w (or (width win) 78)))
(clear win) (clear win)
(box win 0 0) (box win 0 0)
(add-string win 1 1 "[1] Providers [2] Cascade [3] Models [4] View [q] Back") (add-string win "[1] Providers [2] Cascade [3] Models [4] View [q] Back" :y 1 :x 1)
(add-string win 2 1 (format nil " Cascade order: ~{~a~^ → ~}" (add-string win (format nil " Cascade order: ~{~a~^ → ~}"
(mapcar (lambda (k) (string-downcase (string k))) *provider-cascade*))) (mapcar (lambda (k) (string-downcase (string k))) *provider-cascade*)) :y 2 :x 1)
(add-string win 3 1 " Set PROVIDER_CASCADE in .env to reorder.") (add-string win " Set PROVIDER_CASCADE in .env to reorder." :y 3 :x 1)
(refresh win))) (refresh win)))
(defun config-render-models (win) (defun config-render-models (win)
@@ -193,19 +196,19 @@
(let ((w (or (width win) 78))) (let ((w (or (width win) 78)))
(clear win) (clear win)
(box win 0 0) (box win 0 0)
(add-string win 1 1 "[1] Providers [2] Cascade [3] Models [4] View [q] Back") (add-string win "[1] Providers [2] Cascade [3] Models [4] View [q] Back" :y 1 :x 1)
(let ((y 2)) (let ((y 2))
(dolist (slot '(:code :chat :plan :background)) (dolist (slot '(:code :chat :plan :background))
(add-string win y 1 (format nil " ~:@(~a~)" slot)) (add-string win (format nil " ~:@(~a~)" slot) :y y :x 1)
(incf y) (incf y)
(let ((desc (cdr (or (assoc slot *slot-descriptions*) '(:fallback . "General purpose"))))) (let ((desc (cdr (or (assoc slot *slot-descriptions*) '(:fallback . "General purpose")))))
(add-string win y 1 (subseq desc 0 (min (length desc) (- w 4)))) (add-string win (subseq desc 0 (min (length desc) (- w 4))) :y y :x 1)
(incf y)) (incf y))
(dolist (rec (model-explorer-recommend slot)) (dolist (rec (model-explorer-recommend slot))
(let ((label (format nil " ~a (~a ctx)" (let ((label (format nil " ~a (~a ctx)"
(getf rec :name) (getf rec :name)
(if (getf rec :context) (format nil "~dK" (floor (getf rec :context) 1000)) "?")))) (if (getf rec :context) (format nil "~dK" (floor (getf rec :context) 1000)) "?"))))
(add-string win y 1 (subseq label 0 (min (length label) (- w 4)))) (add-string win (subseq label 0 (min (length label) (- w 4))) :y y :x 1)
(incf y)))))) (incf y))))))
(refresh win))) (refresh win)))
@@ -214,12 +217,12 @@
(let ((w (or (width win) 78))) (let ((w (or (width win) 78)))
(clear win) (clear win)
(box win 0 0) (box win 0 0)
(add-string win 1 1 "[1] Providers [2] Cascade [3] Models [4] View [q] Back") (add-string win "[1] Providers [2] Cascade [3] Models [4] View [q] Back" :y 1 :x 1)
(add-string win 2 1 (format nil " Active backends: ~a" (add-string win (format nil " Active backends: ~a"
(loop for k being the hash-keys of *probabilistic-backends* collect k))) (loop for k being the hash-keys of *probabilistic-backends* collect k)) :y 2 :x 1)
(add-string win 3 1 (format nil " Cascade: ~{~a~^, ~}" (add-string win (format nil " Cascade: ~{~a~^, ~}"
(mapcar (lambda (k) (string-downcase (string k))) *provider-cascade*))) (mapcar (lambda (k) (string-downcase (string k))) *provider-cascade*)) :y 3 :x 1)
(add-string win 4 1 (format nil " Model selector: ~a" (if (boundp '*model-selector*) (symbol-value '*model-selector*) "none"))) (add-string win (format nil " Model selector: ~a" (if (boundp '*model-selector*) (symbol-value '*model-selector*) "none")) :y 4 :x 1)
(refresh win))) (refresh win)))
(defun connect-daemon (&optional (host "127.0.0.1") (port 9105)) (defun connect-daemon (&optional (host "127.0.0.1") (port 9105))
@@ -254,7 +257,11 @@
(status-win (make-instance 'window :height 3 :width (- w 2) :y 0 :x 1)) (status-win (make-instance 'window :height 3 :width (- w 2) :y 0 :x 1))
(chat-win (make-instance 'window :height chat-h :width (- w 2) :y 3 :x 1)) (chat-win (make-instance 'window :height chat-h :width (- w 2) :y 3 :x 1))
(config-win (make-instance 'window :height 0 :width (- w 2) :y (- h input-h config-h 1) :x 1)) (config-win (make-instance 'window :height 0 :width (- w 2) :y (- h input-h config-h 1) :x 1))
(input-win (make-instance 'window :height 1 :width (- w 2) :y (- h 1) :x 1))) (input-win (make-instance 'window :height 1 :width (- w 2) :y (- h 1) :x 1)))
;; Enable function key processing (must be set per-window)
(setf (input-blocking input-win) nil)
(setf (function-keys-enabled-p input-win) t)
(setf (function-keys-enabled-p chat-win) t)
(setf *is-running* t *tui-mode* :chat *input-buffer* nil) (setf *is-running* t *tui-mode* :chat *input-buffer* nil)
(connect-daemon) (connect-daemon)
;; First-run: no providers configured → show welcome ;; First-run: no providers configured → show welcome
@@ -273,11 +280,12 @@
(or (proto-get p :text) (format nil "~a" msg)))) (or (proto-get p :text) (format nil "~a" msg))))
*chat-history*)) *chat-history*))
;; handle input ;; handle input
(let ((ch (get-char input-win))) (let ((ch (get-char input-win :timeout 0.1)))
(when (and ch (not (equal ch -1))) (when (and ch (not (equal ch -1)))
(log-message "KEY: ~s type=~s" ch (type-of ch))
(cond (cond
;; F2: toggle config panel ;; F2 (or any integer key >= 265): toggle config panel
((or (eq ch :f2) (and (integerp ch) (= ch 265))) ((and (integerp ch) (>= ch 265) (<= ch 280))
(if (eq *tui-mode* :chat) (if (eq *tui-mode* :chat)
(progn (progn
(setf *tui-mode* :config) (setf *tui-mode* :config)

View File

@@ -23,7 +23,10 @@ The TUI Client is a Croatoan-based ncurses chat interface for Passepartout. It c
** State ** State
#+begin_src lisp #+begin_src lisp
(in-package :passepartout) (defpackage :passepartout.gateway-tui
(:use :cl :croatoan :passepartout :usocket :bordeaux-threads)
(:export :tui-main))
(in-package :passepartout.gateway-tui)
(defvar *stream* nil "TCP stream to daemon") (defvar *stream* nil "TCP stream to daemon")
(defvar *input-buffer* nil "Current input line as reversed char list") (defvar *input-buffer* nil "Current input line as reversed char list")
@@ -137,12 +140,12 @@ The TUI Client is a Croatoan-based ncurses chat interface for Passepartout. It c
(w (or (width win) 78))) (w (or (width win) 78)))
(clear win) (clear win)
(box win 0 0) (box win 0 0)
(add-string win 1 1 (format nil " Passepartout ~a [~a] msgs:~a scroll:~a" (add-string win (format nil " Passepartout ~a [~a] msgs:~a scroll:~a"
(if *stream* "● Connected" "○ Disconnected") (if *stream* "● Connected" "○ Disconnected")
(ecase *tui-mode* (:chat "CHAT") (:config "CONFIG")) (ecase *tui-mode* (:chat "CHAT") (:config "CONFIG"))
(length *chat-history*) (length *chat-history*)
(if (> *chat-scroll-pos* 0) (format nil "~a↑" *chat-scroll-pos*) "0"))) (if (> *chat-scroll-pos* 0) (format nil "~a↑" *chat-scroll-pos*) "0")) :y 1 :x 1)
(add-string win 2 1 (format nil " ~a" (timestamp))) (add-string win (format nil " ~a" (timestamp)) :y 2 :x 1)
(refresh win))) (refresh win)))
(defun chat-render (win h) (defun chat-render (win h)
@@ -160,14 +163,14 @@ The TUI Client is a Croatoan-based ncurses chat interface for Passepartout. It c
(text (cdr entry)) (text (cdr entry))
(prefix (if (eq dir :sent) "⬆" "⬇")) (prefix (if (eq dir :sent) "⬆" "⬇"))
(label (format nil "~a [~a] ~a" prefix (timestamp) text))) (label (format nil "~a [~a] ~a" prefix (timestamp) text)))
(add-string win 1 y label) (add-string win label :y 1 :x y)
(incf y)))))) (incf y))))))
(refresh win)) (refresh win))
(defun input-render (win) (defun input-render (win)
"Draw the input line." "Draw the input line."
(clear win) (clear win)
(add-string win 0 0 (input-text)) (add-string win (input-text) :y 0 :x 0)
(refresh win)) (refresh win))
#+end_src #+end_src
@@ -182,39 +185,39 @@ The TUI Client is a Croatoan-based ncurses chat interface for Passepartout. It c
(let ((w (or (width win) 78))) (let ((w (or (width win) 78)))
(clear win) (clear win)
(box win 0 0) (box win 0 0)
(add-string win 1 1 "[1] Providers [2] Cascade [3] Models [4] View [q] Back") (add-string win "[1] Providers [2] Cascade [3] Models [4] View [q] Back" :y 1 :x 1)
(add-string win 2 1 (format nil " Set provider: ~a" (add-string win (format nil " Set provider: ~a"
(mapcar #'config-provider-line *provider-cascade*))) (mapcar #'config-provider-line *provider-cascade*)) :y 2 :x 1)
;; Show unconfigured but available providers on line 3-8 ;; Show unconfigured but available providers on line 3-8
(let ((y 3) (let ((y 3)
(unconf (remove-if (lambda (p) (provider-available-p p)) (unconf (remove-if (lambda (p) (provider-available-p p))
(mapcar #'car *provider-configs*))) (mapcar #'car *provider-configs*)))
(conf (count-if #'provider-available-p (mapcar #'car *provider-configs*)))) (conf (count-if #'provider-available-p (mapcar #'car *provider-configs*))))
(add-string win 1 (- w 14) (format nil "~a/~a active" conf (length *provider-configs*))) (add-string win (format nil "~a/~a active" conf (length *provider-configs*)) :y 1 :x (- w 14))
(when (zerop conf) (when (zerop conf)
(add-string win y 1 "** No providers configured. Press 1 to set up.") (add-string win "** No providers configured. Press 1 to set up." :y y :x 1)
(incf y)) (incf y))
(when unconf (when unconf
(add-string win y 1 (format nil "Available: ~{~a~^, ~}" (add-string win (format nil "Available: ~{~a~^, ~}"
(mapcar (lambda (k) (string-downcase (string k))) unconf))) (mapcar (lambda (k) (string-downcase (string k))) unconf)) :y y :x 1)
(incf y)) (incf y))
(add-string win y 1 (format nil "Free tier: openrouter (openrouter.ai), gemma-4 from google")) (add-string win (format nil "Free tier: openrouter (openrouter.ai), gemma-4 from google") :y y :x 1)
(refresh win))) (refresh win))))
(defun config-render-providers (win) (defun config-render-providers (win)
"Show all providers with availability status." "Show all providers with availability status."
(let ((w (or (width win) 78))) (let ((w (or (width win) 78)))
(clear win) (clear win)
(box win 0 0) (box win 0 0)
(add-string win 1 1 "[1] Providers [2] Cascade [3] Models [4] View [q] Back") (add-string win "[1] Providers [2] Cascade [3] Models [4] View [q] Back" :y 1 :x 1)
(add-string win 2 1 " Provider Status Key Env") (add-string win " Provider Status Key Env" :y 2 :x 1)
(loop for entry in *provider-configs* (loop for entry in *provider-configs*
for i from 3 for i from 3
do (let* ((provider (car entry)) do (let* ((provider (car entry))
(config (cdr entry)) (config (cdr entry))
(avail (provider-available-p provider)) (avail (provider-available-p provider))
(key-env (or (getf config :key-env) (getf config :url-env) "--"))) (key-env (or (getf config :key-env) (getf config :url-env) "--")))
(add-string win i 1 (format nil " ~:[ ✗~; ✓~] ~20@(~a~) ~a" avail provider key-env)))) (add-string win (format nil " ~:[ ✗~; ✓~] ~20a ~a" avail provider key-env) :y i :x 1))
(refresh win))) (refresh win)))
(defun config-render-cascade (win) (defun config-render-cascade (win)
@@ -222,10 +225,10 @@ The TUI Client is a Croatoan-based ncurses chat interface for Passepartout. It c
(let ((w (or (width win) 78))) (let ((w (or (width win) 78)))
(clear win) (clear win)
(box win 0 0) (box win 0 0)
(add-string win 1 1 "[1] Providers [2] Cascade [3] Models [4] View [q] Back") (add-string win "[1] Providers [2] Cascade [3] Models [4] View [q] Back" :y 1 :x 1)
(add-string win 2 1 (format nil " Cascade order: ~{~a~^ → ~}" (add-string win (format nil " Cascade order: ~{~a~^ → ~}"
(mapcar (lambda (k) (string-downcase (string k))) *provider-cascade*))) (mapcar (lambda (k) (string-downcase (string k))) *provider-cascade*)) :y 2 :x 1)
(add-string win 3 1 " Set PROVIDER_CASCADE in .env to reorder.") (add-string win " Set PROVIDER_CASCADE in .env to reorder." :y 3 :x 1)
(refresh win))) (refresh win)))
(defun config-render-models (win) (defun config-render-models (win)
@@ -233,19 +236,19 @@ The TUI Client is a Croatoan-based ncurses chat interface for Passepartout. It c
(let ((w (or (width win) 78))) (let ((w (or (width win) 78)))
(clear win) (clear win)
(box win 0 0) (box win 0 0)
(add-string win 1 1 "[1] Providers [2] Cascade [3] Models [4] View [q] Back") (add-string win "[1] Providers [2] Cascade [3] Models [4] View [q] Back" :y 1 :x 1)
(let ((y 2)) (let ((y 2))
(dolist (slot '(:code :chat :plan :background)) (dolist (slot '(:code :chat :plan :background))
(add-string win y 1 (format nil " ~:@(~a~)" slot)) (add-string win (format nil " ~:@(~a~)" slot) :y y :x 1)
(incf y) (incf y)
(let ((desc (cdr (or (assoc slot *slot-descriptions*) '(:fallback . "General purpose"))))) (let ((desc (cdr (or (assoc slot *slot-descriptions*) '(:fallback . "General purpose")))))
(add-string win y 1 (subseq desc 0 (min (length desc) (- w 4)))) (add-string win (subseq desc 0 (min (length desc) (- w 4))) :y y :x 1)
(incf y)) (incf y))
(dolist (rec (model-explorer-recommend slot)) (dolist (rec (model-explorer-recommend slot))
(let ((label (format nil " ~a (~a ctx)" (let ((label (format nil " ~a (~a ctx)"
(getf rec :name) (getf rec :name)
(if (getf rec :context) (format nil "~dK" (floor (getf rec :context) 1000)) "?")))) (if (getf rec :context) (format nil "~dK" (floor (getf rec :context) 1000)) "?"))))
(add-string win y 1 (subseq label 0 (min (length label) (- w 4)))) (add-string win (subseq label 0 (min (length label) (- w 4))) :y y :x 1)
(incf y)))))) (incf y))))))
(refresh win))) (refresh win)))
@@ -254,12 +257,12 @@ The TUI Client is a Croatoan-based ncurses chat interface for Passepartout. It c
(let ((w (or (width win) 78))) (let ((w (or (width win) 78)))
(clear win) (clear win)
(box win 0 0) (box win 0 0)
(add-string win 1 1 "[1] Providers [2] Cascade [3] Models [4] View [q] Back") (add-string win "[1] Providers [2] Cascade [3] Models [4] View [q] Back" :y 1 :x 1)
(add-string win 2 1 (format nil " Active backends: ~a" (add-string win (format nil " Active backends: ~a"
(loop for k being the hash-keys of *probabilistic-backends* collect k))) (loop for k being the hash-keys of *probabilistic-backends* collect k)) :y 2 :x 1)
(add-string win 3 1 (format nil " Cascade: ~{~a~^, ~}" (add-string win (format nil " Cascade: ~{~a~^, ~}"
(mapcar (lambda (k) (string-downcase (string k))) *provider-cascade*))) (mapcar (lambda (k) (string-downcase (string k))) *provider-cascade*)) :y 3 :x 1)
(add-string win 4 1 (format nil " Model selector: ~a" (if (boundp '*model-selector*) (symbol-value '*model-selector*) "none"))) (add-string win (format nil " Model selector: ~a" (if (boundp '*model-selector*) (symbol-value '*model-selector*) "none")) :y 4 :x 1)
(refresh win))) (refresh win)))
#+end_src #+end_src
@@ -300,7 +303,11 @@ The TUI Client is a Croatoan-based ncurses chat interface for Passepartout. It c
(status-win (make-instance 'window :height 3 :width (- w 2) :y 0 :x 1)) (status-win (make-instance 'window :height 3 :width (- w 2) :y 0 :x 1))
(chat-win (make-instance 'window :height chat-h :width (- w 2) :y 3 :x 1)) (chat-win (make-instance 'window :height chat-h :width (- w 2) :y 3 :x 1))
(config-win (make-instance 'window :height 0 :width (- w 2) :y (- h input-h config-h 1) :x 1)) (config-win (make-instance 'window :height 0 :width (- w 2) :y (- h input-h config-h 1) :x 1))
(input-win (make-instance 'window :height 1 :width (- w 2) :y (- h 1) :x 1))) (input-win (make-instance 'window :height 1 :width (- w 2) :y (- h 1) :x 1)))
;; Enable function key processing (must be set per-window)
(setf (input-blocking input-win) nil)
(setf (function-keys-enabled-p input-win) t)
(setf (function-keys-enabled-p chat-win) t)
(setf *is-running* t *tui-mode* :chat *input-buffer* nil) (setf *is-running* t *tui-mode* :chat *input-buffer* nil)
(connect-daemon) (connect-daemon)
;; First-run: no providers configured → show welcome ;; First-run: no providers configured → show welcome
@@ -319,11 +326,12 @@ The TUI Client is a Croatoan-based ncurses chat interface for Passepartout. It c
(or (proto-get p :text) (format nil "~a" msg)))) (or (proto-get p :text) (format nil "~a" msg))))
*chat-history*)) *chat-history*))
;; handle input ;; handle input
(let ((ch (get-char input-win))) (let ((ch (get-char input-win :timeout 0.1)))
(when (and ch (not (equal ch -1))) (when (and ch (not (equal ch -1)))
(log-message "KEY: ~s type=~s" ch (type-of ch))
(cond (cond
;; F2: toggle config panel ;; F2 (or any integer key >= 265): toggle config panel
((or (eq ch :f2) (and (integerp ch) (= ch 265))) ((and (integerp ch) (>= ch 265) (<= ch 280))
(if (eq *tui-mode* :chat) (if (eq *tui-mode* :chat)
(progn (progn
(setf *tui-mode* :config) (setf *tui-mode* :config)

View File

@@ -404,7 +404,10 @@ case "$COMMAND" in
exec sbcl \ exec sbcl \
--eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' \ --eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' \
--eval '(ql:quickload :passepartout/tui)' \ --eval '(ql:quickload :passepartout/tui)' \
--eval '(passepartout.gateway-tui:main)' --eval '(in-package :passepartout)' \
--eval "(load (format nil \"~alisp/system-model-provider.lisp\" (truename \"$PASSEPARTOUT_DATA_DIR/\")))" \
--eval "(load (format nil \"~alisp/system-model-explorer.lisp\" (truename \"$PASSEPARTOUT_DATA_DIR/\")))" \
--eval '(passepartout.gateway-tui:tui-main)'
;; ;;
gateway) gateway)
SUBCMD=$1; PLATFORM=$2; TOKEN=$3 SUBCMD=$1; PLATFORM=$2; TOKEN=$3