@lamemind/loom-deck 0.44.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/viewport.js CHANGED
@@ -223,23 +223,22 @@ export function readerCapacity(rows) {
223
223
  // 1 riga hint
224
224
  // 1 marginTop del box testo
225
225
  // 2 bordi del box testo
226
- // 1 marginTop della riga azioni
227
- // 1 riga modello (T108)
228
- // 1 riga nota (T111)
229
- // 1 riga azioni
226
+ // 1 marginTop dell'area di compilazione
227
+ // 1 riga azione (T66)
228
+ // 1 riga prompt (T117)
229
+ // 1 riga modello (T108)
230
+ // 1 riga titolo (T111)
230
231
  //
231
- // Le ultime quattro sono il motivo per cui il detail non può riusare
232
- // READER_CHROME: barra bottoni, selettore modello e campo nota sono righe FISSE
233
- // in più dentro l'overlay, e ogni riga fissa aggiunta va scalata dalla capienza
234
- // del contenuto o il frame sfonda `rows` (stessa invariante di
235
- // TASKS_PANE_CHROME).
232
+ // Le ultime cinque sono il motivo per cui il detail non può riusare
233
+ // READER_CHROME: le quattro righe dell'area di compilazione sono righe FISSE in
234
+ // più dentro l'overlay, e ogni riga fissa aggiunta va scalata dalla capienza del
235
+ // contenuto o il frame sfonda `rows` (stessa invariante di TASKS_PANE_CHROME).
236
236
  //
237
- // La nota è una riga fissa e non un secondo `extra` di `detailCapacity` (T111 ·
238
- // D1): due condizionali mutuamente esclusivi obbligherebbero il parametro a dire
239
- // QUALE è aperto — un solo booleano per entrambi sottostima quando quello aperto
240
- // è il più alto, e sommarli toglie righe al testo per un campo che non è a
241
- // schermo.
242
- const DETAIL_CHROME = 12;
237
+ // Sono fisse e non `extra` condizionali di `detailCapacity` (T111 · D1): due
238
+ // condizionali mutuamente esclusivi obbligherebbero il parametro a dire QUALE è
239
+ // a schermo — un solo booleano per entrambi sottostima quando quello aperto è il
240
+ // più alto, e sommarli toglie righe al testo per un campo che non c'è.
241
+ const DETAIL_CHROME = 13;
243
242
  // T91 — la ricerca dentro il detail: marginTop + riga del campo.
244
243
  //
245
244
  // `MODAL_HEIGHT` dice che il detail costa 0 ai due pane (li sostituisce), e da
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lamemind/loom-deck",
3
- "version": "0.44.0",
3
+ "version": "0.45.0",
4
4
  "description": "Deck TUI Ink per-progetto della famiglia loom: legge tasks.md e spawna sessioni Claude Code bound via LOOM_TASK",
5
5
  "type": "module",
6
6
  "bin": {
package/scripts/deck-run CHANGED
@@ -11,14 +11,18 @@
11
11
  # Quattro assi ORTOGONALI, da non confondere fra loro:
12
12
  # binding task <TaskID> ..... --no-task
13
13
  # continuità nuova ........ --resume [--fork]
14
- # prompt --prompt-kind none|recap|preflight|run|checkpoint
14
+ # prompt --prompt-kind none|recap|preflight|run|checkpoint | --prompt <testo>
15
15
  # modello --model fable|opus|sonnet|haiku
16
16
  # Il terzo asse (T56) prima non esisteva: il prompt era una CONSEGUENZA degli
17
17
  # altri due (bound ⇒ recap, --no-task ⇒ niente, --resume ⇒ niente), quindi
18
18
  # "task-bound SENZA prompt" era inesprimibile e l'unico modo di non avere un
19
- # prompt era perdere la task. Il catalogo dei prompt vive qui e non nel deck
20
- # perché questo è il primitive UI-agnostico: il chiamante passa un SIMBOLO, non
21
- # una stringa il quoting resta verificato in un posto solo (vedi PROMPT sotto).
19
+ # prompt era perdere la task. Il kind è un SIMBOLO e il suo testo sta nel
20
+ # catalogo condiviso `prompt-catalog` (sibling di questo script).
21
+ # T117accanto al kind c'è ora `--prompt <testo>`, che passa il prompt
22
+ # LETTERALE. I due sono mutuamente esclusivi: sono due modi di dire la stessa
23
+ # cosa, e accettarli insieme costringerebbe a stabilire quale vince. Serve a chi
24
+ # mostra il prompt all'utente prima dello spawn e glielo lascia modificare —
25
+ # dopo una modifica nessun kind descrive più quel testo.
22
26
  # Il quarto asse (T108) è indipendente dagli altri tre: vale sul ramo bound come
23
27
  # su --no-task, su una sessione nuova come su una ripresa.
24
28
  #
@@ -56,6 +60,12 @@
56
60
  #
57
61
  set -euo pipefail
58
62
 
63
+ # Il catalogo dei prompt è un file DATI sibling di questo script, non un `case`
64
+ # qui dentro: lo legge anche il deck TUI, che con la stessa risoluzione sibling
65
+ # arriva allo stesso file della stessa installazione.
66
+ _SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
67
+ PROMPT_CATALOG="${LOOM_DECK_PROMPT_CATALOG:-${_SCRIPT_DIR}/prompt-catalog}"
68
+
59
69
  TASK=""
60
70
  SESSION_ID=""
61
71
  RESUME_ID=""
@@ -66,6 +76,11 @@ TITLE_NOTE=""
66
76
  # "kind implicito" da "kind chiesto", perché con --no-task il primo è legittimo
67
77
  # (nessun prompt, come sempre) e il secondo è un errore d'uso.
68
78
  PROMPT_KIND=""
79
+ # T117 — prompt letterale. Vuoto NON basta a dire "non passato": `--prompt ''`
80
+ # è una richiesta legittima (nessun prompt, deciso da chi ha svuotato il campo),
81
+ # quindi la presenza del flag si tiene in un booleano a parte.
82
+ PROMPT_TEXT=""
83
+ PROMPT_GIVEN=0
69
84
  # Vuoto = flag non passato: la cascata (env, poi default) si risolve più sotto,
70
85
  # insieme alla validazione, così l'ingresso da argomento e quello da env passano
71
86
  # per lo stesso enum.
@@ -117,6 +132,10 @@ while [[ $# -gt 0 ]]; do
117
132
  PROMPT_KIND="${2:-}"; shift 2 ;;
118
133
  --prompt-kind=*)
119
134
  PROMPT_KIND="${1#*=}"; shift ;;
135
+ --prompt)
136
+ PROMPT_TEXT="${2:-}"; PROMPT_GIVEN=1; shift 2 ;;
137
+ --prompt=*)
138
+ PROMPT_TEXT="${1#*=}"; PROMPT_GIVEN=1; shift ;;
120
139
  --session-id)
121
140
  SESSION_ID="${2:-}"; shift 2 ;;
122
141
  --session-id=*)
@@ -150,6 +169,9 @@ USAGE="uso: deck-run <TaskID> [--model <alias>] [--prompt-kind <kind>] [--sessio
150
169
  run /loom-works:run-task <TaskID>
151
170
  checkpoint /loom-works:checkpoint-task <TaskID>
152
171
 
172
+ --prompt prompt LETTERALE, per chi lo ha già composto (o fatto
173
+ modificare). Esclusivo con --prompt-kind; vuoto = nessun prompt
174
+
153
175
  --model modello della sessione: fable|opus|sonnet|haiku (default: opus)
154
176
  valore ignoto → fallback sul default, con avviso su stderr
155
177
 
@@ -195,6 +217,18 @@ if [[ $NO_TASK -eq 1 && -n "$PROMPT_KIND" ]]; then
195
217
  echo "$USAGE" >&2
196
218
  exit 2
197
219
  fi
220
+ if [[ $NO_TASK -eq 1 && $PROMPT_GIVEN -eq 1 ]]; then
221
+ echo "--prompt richiede una task: una sessione --no-task non ha prompt iniziale" >&2
222
+ echo "$USAGE" >&2
223
+ exit 2
224
+ fi
225
+ # Due modi di dire la stessa cosa: accettarli insieme obbligherebbe a stabilire
226
+ # quale vince, cioè a scrivere una precedenza che nessun chiamante ha chiesto.
227
+ if [[ $PROMPT_GIVEN -eq 1 && -n "$PROMPT_KIND" ]]; then
228
+ echo "--prompt e --prompt-kind sono mutuamente esclusivi (il testo o il simbolo, non entrambi)" >&2
229
+ echo "$USAGE" >&2
230
+ exit 2
231
+ fi
198
232
 
199
233
  MODE="${LOOM_DECK_SPAWN_MODE:-inline}"
200
234
  PROFILE_UUID="${LOOM_DECK_PROFILE_UUID:-5a36ae48df1c4d4882f43060e3e59656}"
@@ -381,7 +415,7 @@ RES_FLAG=""
381
415
  FORK_FLAG=""
382
416
  [[ $FORK -eq 1 ]] && FORK_FLAG="--fork-session "
383
417
 
384
- # ── Catalogo dei prompt iniziali (T56) ───────────────────────────────────────
418
+ # ── Catalogo dei prompt iniziali (T56 · T117) ────────────────────────────────
385
419
  # Il prompt iniziale è la scelta di CHI APRE la sessione, non una proprietà della
386
420
  # task: lo stesso task file si apre per leggerne lo stato, per un preflight, per
387
421
  # eseguirlo o a mani nude. Il kind è quell'intento.
@@ -391,28 +425,53 @@ FORK_FLAG=""
391
425
  # preflight → skill di preflight sulla task
392
426
  # run → skill di esecuzione sulla task
393
427
  # checkpoint → skill di checkpoint sulla task
394
- # Vincolo di quoting: nessun template deve contenere apici singoli (viaggia
395
- # dentro '...' nel comando in-tab). `/loom-works:...` passa intero proprio perché
396
- # è single-quoted lo slash e i due punti non sono metacaratteri dentro.
428
+ #
429
+ # T117 i testi non stanno più qui ma nel file dati `prompt-catalog`, sibling di
430
+ # questo script. Il deck TUI li mostra nel campo prompt del detail prima dello
431
+ # spawn, e per farlo deve leggerli: come `case` bash sarebbe una seconda scrittura
432
+ # delle stesse regole. Il file resta l'unica copia, letta da entrambi i lati.
433
+ # Formato a righe e non JSON perché `jq` è dipendenza OPZIONALE di questo script
434
+ # (con fallback) e leggere il catalogo lo renderebbe dura.
397
435
  #
398
436
  # Precedenza con l'env: LOOM_DECK_ENTER_PROMPT VINCE sul kind (retro-compat: chi
399
437
  # lo usa oggi non si accorge del cambio), tranne su `none` — chiedere
400
438
  # esplicitamente nessun prompt non è una richiesta che un default d'ambiente
401
- # possa scavalcare. Il placeholder {TASK} vale solo per il valore dell'override;
402
- # i template del catalogo interpolano ${TASK} diretto (nel `${VAR:-...}` la `}`
403
- # di `{TASK}` chiuderebbe in anticipo l'espansione).
404
- # Ogni kind è un template diretto sull'id: la lista del deck è monofamiglia
439
+ # possa scavalcare. Non vince invece su `--prompt`, che è una richiesta esplicita
440
+ # e già risolta: un override d'ambiente che scavalca un testo composto a mano
441
+ # butterebbe via proprio la modifica che il flag esiste per portare.
442
+ # Ogni template nomina il TaskID via `{TASK}`: la lista del deck è monofamiglia
405
443
  # (`T`, unico prefisso del contratto loom), quindi nessun prompt discrimina sulla
406
444
  # forma dell'id.
407
- case "${PROMPT_KIND:-recap}" in
408
- none) PROMPT="" ;;
409
- recap) PROMPT="recap stato task ${TASK}" ;;
410
- preflight) PROMPT="/loom-works:preflight-task ${TASK}" ;;
411
- checkpoint) PROMPT="/loom-works:checkpoint-task ${TASK}" ;;
412
- run) PROMPT="/loom-works:run-task ${TASK}" ;;
413
- esac
414
- if [[ "${PROMPT_KIND:-}" != "none" && -n "${LOOM_DECK_ENTER_PROMPT:-}" ]]; then
415
- PROMPT="${LOOM_DECK_ENTER_PROMPT//\{TASK\}/${TASK}}"
445
+ _prompt_template() { # <kind> → template col placeholder {TASK}, o niente
446
+ local want="$1" k t
447
+ [[ -f "$PROMPT_CATALOG" ]] || return 1
448
+ while IFS=$'\t' read -r k t || [[ -n "$k" ]]; do
449
+ [[ -z "$k" || "$k" == \#* ]] && continue
450
+ [[ "$k" == "$want" ]] && { printf '%s' "$t"; return 0; }
451
+ done < "$PROMPT_CATALOG"
452
+ return 1
453
+ }
454
+
455
+ if [[ $PROMPT_GIVEN -eq 1 ]]; then
456
+ PROMPT="$PROMPT_TEXT"
457
+ else
458
+ _kind="${PROMPT_KIND:-recap}"
459
+ if [[ "$_kind" == "none" ]]; then
460
+ PROMPT=""
461
+ else
462
+ # Catalogo assente o voce mancante: il kind è già passato dall'enum sopra,
463
+ # quindi qui manca il FILE — un'installazione mutila, non un errore d'uso.
464
+ # Si degrada a nessun prompt dicendolo, invece di aprire una sessione con un
465
+ # prompt inventato al volo (che sarebbe la copia del catalogo).
466
+ if ! PROMPT="$(_prompt_template "$_kind")"; then
467
+ echo "catalogo prompt non leggibile ('${PROMPT_CATALOG}'): sessione senza prompt iniziale" >&2
468
+ PROMPT=""
469
+ fi
470
+ PROMPT="${PROMPT//\{TASK\}/${TASK}}"
471
+ fi
472
+ if [[ "$_kind" != "none" && -n "${LOOM_DECK_ENTER_PROMPT:-}" ]]; then
473
+ PROMPT="${LOOM_DECK_ENTER_PROMPT//\{TASK\}/${TASK}}"
474
+ fi
416
475
  fi
417
476
 
418
477
  # Comando eseguito dentro la tab: bind LOOM_TASK per la sessione, avvia CC sul PROMPT.
@@ -427,8 +486,21 @@ fi
427
486
  # fork (T28) ricade nello stesso ramo — è una ripresa, con id nuovo.
428
487
  # Il kind `none` (T56) atterra sulla stessa assenza per una ragione diversa: lì
429
488
  # la sessione è nuova e bound, ma l'utente ha chiesto di entrarci a mani nude.
489
+ #
490
+ # QUOTING DEL PROMPT — il testo entra in `bash -lc "$IN_TAB_CMD"`, cioè in una
491
+ # riga che una shell PARSA. Finché i template erano scritti qui dentro bastava il
492
+ # vincolo "nessun apice singolo nel catalogo"; da T117 il testo può arrivare da
493
+ # `--prompt`, cioè digitato a mano dall'utente nel campo del detail — e lì un
494
+ # apice singolo chiuderebbe la stringa, consegnando alla shell tutto ciò che
495
+ # segue come comando. Si passa quindi per la stessa forma canonica di
496
+ # `shellQuote` in spawn.ts: apice chiuso, apice letterale escapato, apice
497
+ # riaperto (`'\''`). Dentro apici singoli nessun altro carattere resta speciale,
498
+ # quindi non c'è nient'altro da enumerare — newline compresi.
499
+ _shell_quote() { # <testo> → il testo come argomento singolo per una shell POSIX
500
+ printf "'%s'" "${1//\'/\'\\\'\'}"
501
+ }
430
502
  PROMPT_ARG=""
431
- [[ -n "$PROMPT" && -z "$RESUME_ID" ]] && PROMPT_ARG="'${PROMPT}'"
503
+ [[ -n "$PROMPT" && -z "$RESUME_ID" ]] && PROMPT_ARG="$(_shell_quote "$PROMPT")"
432
504
  # Il SID_FLAG entra anche nel ramo --no-task, che altrimenti lo scarta insieme
433
505
  # agli altri elementi task-bound: nel fork di una sessione SPOT il sessionId
434
506
  # pinnato non serve a legare una task (non c'è) ma a rendere noto in anticipo
@@ -0,0 +1,21 @@
1
+ # Catalogo dei prompt iniziali di una sessione Claude aperta dal deck.
2
+ #
3
+ # Formato a righe: <kind><TAB><template>, con `{TASK}` interpolato col TaskID.
4
+ # Righe vuote e righe che iniziano con `#` sono commenti.
5
+ #
6
+ # Perché un file dati e non un `case` dentro deck-run: due lati hanno bisogno
7
+ # dello stesso testo — deck-run per comporre il comando in-tab, il deck TUI per
8
+ # pre-riempire il campo prompt del detail con ciò che sta per essere eseguito.
9
+ # Come codice, il secondo lato dovrebbe riscriverlo, e le due copie divergono
10
+ # alla prima voce aggiunta da una parte sola.
11
+ #
12
+ # `none` non compare: nessun prompt non è un template vuoto, è l'assenza della
13
+ # voce. Chi chiede un kind fuori da questo file riceve un prompt vuoto.
14
+ #
15
+ # Vincolo sui template: nessun apice singolo. Il testo finisce dentro `'...'`
16
+ # nel comando passato a `bash -lc`, e questo file è committato — un apice qui
17
+ # sarebbe un errore di scrittura, non un input da quotare.
18
+ recap recap stato task {TASK}
19
+ preflight /loom-works:preflight-task {TASK}
20
+ run /loom-works:run-task {TASK}
21
+ checkpoint /loom-works:checkpoint-task {TASK}