@lamemind/loom-deck 0.75.0 → 0.77.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.
@@ -0,0 +1,153 @@
1
+ // T176 — le tab di WINDOWS TERMINAL: argv di `wt.exe`, env della tab, il canale
2
+ // che porta l'argv di `claude` al wrapper.
3
+ //
4
+ // Su Windows una conversazione si apre con
5
+ //
6
+ // wt.exe -w 0 new-tab -p loom-deck -d <root> --title <titolo>
7
+ // --suppressApplicationTitle -- <node> <tab-wrapper.js>
8
+ //
9
+ // e l'argv di `claude` (nome, flag, prompt) NON sta su quella riga: viaggia come
10
+ // JSON nella variabile `LOOM_DECK_TAB_ARGV`, perché la tab eredita l'env del
11
+ // processo che lancia `wt.exe` (T174 verdetto 5, rimisurato con `-p`). Il
12
+ // wrapper la legge, la toglie e lancia `claude` senza shell (P3 preflight).
13
+ // Così il prompt non passa né dal parser di `wt` né da una shell: `wt` legge `;`
14
+ // come separatore di sottocomandi in QUALUNQUE argomento, anche dopo `--` e
15
+ // anche escapato `\;` (misurato su Windows Terminal 1.24: la coda dopo il `;`
16
+ // diventa una tab a sé, col proprio errore), e cmd o PowerShell
17
+ // interpreterebbero `' " & % ^ |`.
18
+ //
19
+ // Modulo PURO: l'env arriva come valore, il bash di Git come stringa già
20
+ // risolta.
21
+ /** Il lanciatore di Windows Terminal: l'alias d'esecuzione in `WindowsApps`,
22
+ * che `spawn` trova nel `PATH` e lancia senza shell (misurato). */
23
+ export const WT_CMD = 'wt.exe';
24
+ /** Il profilo delle tab del deck (P9 preflight): uno solo per tutti i
25
+ * progetti, nascosto dal menu, creato a mano dall'utente nel `settings.json` di
26
+ * Windows Terminal. Porta ciò che i flag non dicono — font, `closeOnExit`, la
27
+ * shell del tasto `t`; titolo, cartella e comando li passa il deck. */
28
+ export const WT_PROFILE_DEFAULT = 'loom-deck';
29
+ /** La variabile con cui l'argv di `claude` arriva al wrapper della tab. */
30
+ export const TAB_ARGV_ENV = 'LOOM_DECK_TAB_ARGV';
31
+ /**
32
+ * I marker che una sessione Claude Code inietta nei processi figli (P4
33
+ * preflight, misurati nella shell di una sessione su Windows).
34
+ *
35
+ * La tab eredita tutto l'env del deck, e un deck avviato da una shell aperta
36
+ * dentro Claude Code li porterebbe nella conversazione nuova: con
37
+ * `CLAUDE_CODE_CHILD_SESSION` la sessione non salva il transcript («Transcript
38
+ * saving is off — inherited CLAUDE_CODE_CHILD_SESSION marker») e non compare
39
+ * nello storico del deck. Lista chiusa e non ogni `CLAUDE*`: un
40
+ * `CLAUDE_CONFIG_DIR` o un `CLAUDE_CODE_GIT_BASH_PATH` dell'utente sparirebbero
41
+ * senza segnale, mentre un marker nuovo di una versione futura si annuncia da
42
+ * sé, nel messaggio di avvio di `claude` che lo nomina.
43
+ */
44
+ export const SESSION_MARKERS = [
45
+ 'CLAUDECODE',
46
+ 'CLAUDE_CODE_CHILD_SESSION',
47
+ 'CLAUDE_CODE_SESSION_ID',
48
+ 'CLAUDE_PID',
49
+ 'CLAUDE_EFFORT',
50
+ 'CLAUDE_CODE_MESSAGING_SOCKET',
51
+ 'CLAUDE_CODE_MESSAGING_TOKEN',
52
+ 'CLAUDE_CODE_ENTRYPOINT',
53
+ 'CLAUDE_CODE_SESSION_ATTENDED',
54
+ 'CLAUDE_CODE_EXECPATH',
55
+ ];
56
+ const GIT_BASH_ENV = 'CLAUDE_CODE_GIT_BASH_PATH';
57
+ /** Il profilo da passare a `-p`: `LOOM_DECK_WT_PROFILE` se impostata (vuota =
58
+ * nessun `-p`, il profilo di default di Windows Terminal), altrimenti
59
+ * `loom-deck`. Un profilo che non esiste non è un errore: Windows Terminal
60
+ * apre la tab col profilo di default e non dice niente (misurato), quindi il
61
+ * deck lo passa sempre senza verificarlo. */
62
+ export function wtProfile(env) {
63
+ const v = env.LOOM_DECK_WT_PROFILE;
64
+ if (v === undefined)
65
+ return WT_PROFILE_DEFAULT;
66
+ return v || null;
67
+ }
68
+ /** Il titolo come lo riceve `wt`: senza `;`, che spezzerebbe il comando. Il
69
+ * titolo è composto dal deck (label, TaskID, nota ridotta) e un `;` può
70
+ * venire solo dal nome del progetto: si toglie invece di fallire. */
71
+ export function wtTitle(title) {
72
+ return title.replace(/;/g, '');
73
+ }
74
+ /**
75
+ * L'argv di `wt.exe` per una tab nuova.
76
+ *
77
+ * `-w 0` è la finestra di Windows Terminal usata per ultima — quella da cui si
78
+ * lavora col deck —, `-w new` una finestra nuova (l'equivalente di
79
+ * `--new-window` di Ptyxis). `--suppressApplicationTitle` tiene il titolo del
80
+ * deck contro quello che il programma della tab imposta (misurato): è ciò che su
81
+ * Linux fa `-T` di Ptyxis contro l'OSC del prompt. Senza titolo il flag non si
82
+ * passa, e la tab prende quello della shell.
83
+ */
84
+ export function wtNewTabArgs(tab) {
85
+ const args = ['-w', tab.newWindow ? 'new' : '0', 'new-tab'];
86
+ if (tab.profile)
87
+ args.push('-p', tab.profile);
88
+ args.push('-d', tab.cwd);
89
+ if (tab.title !== null)
90
+ args.push('--title', wtTitle(tab.title), '--suppressApplicationTitle');
91
+ if (tab.command)
92
+ args.push('--', ...tab.command);
93
+ return args;
94
+ }
95
+ /**
96
+ * Il primo argomento di `wt` che contiene un `;`, o `null`.
97
+ *
98
+ * Il titolo è già ripulito; restano i path (la cartella, node, il wrapper) e il
99
+ * nome del profilo. Un path col `;` è legale su NTFS e `wt` non lo sa portare:
100
+ * lo spawn si ferma dicendolo, invece di aprire una tab d'errore con metà
101
+ * comando.
102
+ */
103
+ export function semicolonArg(args) {
104
+ return args.find((a) => a.includes(';')) ?? null;
105
+ }
106
+ /** Toglie una chiave ignorando le maiuscole: su Windows l'env non le distingue,
107
+ * e una copia di `process.env` può portare `Path` invece di `PATH`. */
108
+ function deleteKey(env, key) {
109
+ const want = key.toUpperCase();
110
+ for (const k of Object.keys(env))
111
+ if (k.toUpperCase() === want)
112
+ delete env[k];
113
+ }
114
+ function hasKey(env, key) {
115
+ const want = key.toUpperCase();
116
+ return Object.keys(env).some((k) => k.toUpperCase() === want && env[k]);
117
+ }
118
+ /**
119
+ * L'env di una tab aperta dal deck su Windows: quello del deck, meno i marker di
120
+ * sessione, con la task della conversazione e il bash di Git.
121
+ *
122
+ * `task` = `null` per le tab senza task (`--no-task`, il tasto `t`): una
123
+ * `LOOM_TASK` ereditata legherebbe a una task una sessione che non ne ha, e
124
+ * l'hook `SessionStart` ne inietterebbe il task file. `CLAUDE_CODE_GIT_BASH_PATH`
125
+ * entra quando manca e il bash si trova: la tab eredita anche il `PATH`, e col
126
+ * `PATH` di Git Bash `claude` eseguirebbe gli hook del plugin in PowerShell
127
+ * (T174 verdetto 9), cioè senza la task in contesto.
128
+ */
129
+ export function windowsTabEnv(env, task, gitBash) {
130
+ const out = { ...env };
131
+ for (const marker of SESSION_MARKERS)
132
+ deleteKey(out, marker);
133
+ deleteKey(out, 'LOOM_TASK');
134
+ deleteKey(out, TAB_ARGV_ENV);
135
+ if (task)
136
+ out.LOOM_TASK = task;
137
+ if (gitBash && !hasKey(env, GIT_BASH_ENV))
138
+ out[GIT_BASH_ENV] = gitBash;
139
+ return out;
140
+ }
141
+ /** L'argv di `claude` letto dalla variabile della tab, o `null` se manca o non
142
+ * è un array di stringhe. */
143
+ export function parseTabArgv(raw) {
144
+ if (!raw)
145
+ return null;
146
+ try {
147
+ const v = JSON.parse(raw);
148
+ return Array.isArray(v) && v.every((a) => typeof a === 'string') ? v : null;
149
+ }
150
+ catch {
151
+ return null;
152
+ }
153
+ }
package/dist/wrap-scan.js CHANGED
@@ -87,7 +87,7 @@ export const WRAP_DEFAULT_PATH = '.';
87
87
  * T161 — il buco `{path}` del titolo e del prompt di una sessione di
88
88
  * srotolamento: il path reso a parole.
89
89
  *
90
- * Le barre diventano spazi e l'estensione cade QUI, prima di `_sane_note`, il
90
+ * Le barre diventano spazi e l'estensione cade QUI, prima di `saneNote`, il
91
91
  * cui alfabeto non ha né `/` né `.`: lasciandoceli cadere le parole si
92
92
  * salderebbero (`runtimereferencedoc-systemmd`), e il titolo della tab
93
93
  * divergerebbe da quello che la lista del deck legge dal sidecar.
@@ -145,7 +145,7 @@ export async function runWrapScan(projectRoot) {
145
145
  if (!script)
146
146
  return false;
147
147
  try {
148
- const { stdout } = await execFileAsync('python3', [script, '--root', projectRoot, '--scan'], { cwd: projectRoot, maxBuffer: 16 * 1024 * 1024 });
148
+ const { stdout } = await execFileAsync('python3', [script, '--root', projectRoot, '--scan'], { cwd: projectRoot, maxBuffer: 16 * 1024 * 1024, windowsHide: true });
149
149
  const path = wrapCacheFile(projectRoot);
150
150
  mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
151
151
  writeFileSync(path, stdout, { mode: 0o600 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lamemind/loom-deck",
3
- "version": "0.75.0",
3
+ "version": "0.77.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": {
@@ -13,7 +13,8 @@
13
13
  "scripts": {
14
14
  "dev": "tsx src/cli.tsx",
15
15
  "build": "tsc",
16
- "test": "tsx --test test/*.test.ts",
16
+ "pretest": "tsc",
17
+ "test": "node test/run.mjs",
17
18
  "start": "node dist/cli.js",
18
19
  "prepare": "tsc"
19
20
  },