tabby-agentdeck 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 junggon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,240 @@
1
+ # tabby-agentdeck
2
+
3
+ Tabby 터미널 플러그인. **Claude Code · Codex 같은 AI 에이전트 CLI 를 여러 탭에 띄워 놓고 쓰는 사람**을 위한 것이다.
4
+
5
+ 터미널 뷰포트를 **4:3 으로 고정**하고 남는 공간에 **세션 목록 + 작업 상태 사이드바**를 그린다.
6
+ 어느 에이전트가 돌고 있고 어느 쪽이 내 입력을 기다리는지 탭을 옮겨 다니지 않고 한눈에 본다.
7
+ 거기에 에이전트 CLI 가 터미널에서 겪는 문제들(이미지 붙여넣기, 줄바꿈 키, pty 폭 어긋남)을 같이 손봐 둔다.
8
+
9
+ ```
10
+ ┌──────────────────────────────┬──────────────────┐
11
+ │ │ AGENT DECK 4 │
12
+ │ 터미널 (창 높이 × 4/3) ├──────────────────┤
13
+ │ │ ● 진행중 리팩터링 │
14
+ │ Tabby 순정 탭바는 숨김 │ ⏸ 승인대기 재빌드 │
15
+ │ │ ✔ 완료 테스트 │
16
+ │ │ ○ 대기 │
17
+ │ ├──────────────────┤
18
+ │ │ + 새 탭 │ 설정 │
19
+ └──────────────────────────────┴──────────────────┘
20
+ ```
21
+
22
+ ## 기능
23
+
24
+ | | 동작 |
25
+ |---|---|
26
+ | **4:3 기준 폭 계산** | 창 높이에서 폭을 역산해 `.content.main` 을 고정폭으로 만든다. 창 크기가 바뀌면 `ResizeObserver` 로 다시 계산 |
27
+ | **사이드바 탭 관리** | 클릭=선택, `×`=닫기, 더블클릭=작업 이름 편집, 우클릭=상태 직접 지정 |
28
+ | **스크롤** | 탭이 많아지면 목록 영역만 스크롤 |
29
+ | **작업 상태** | 진행중 / 승인대기 / 완료 / 오류 / 대기 를 색·배지·경과시간으로 표시 |
30
+ | **기본 프로필** | 새 탭을 항상 지정한 작업 루트에서 연다 |
31
+
32
+ ## 설치
33
+
34
+ ```powershell
35
+ npm install
36
+ npm run build
37
+ powershell -NoProfile -ExecutionPolicy Bypass -File install.ps1 -Shortcut
38
+ ```
39
+
40
+ `install.ps1` 은 두 가지를 한다.
41
+
42
+ 1. `%APPDATA%\tabby\plugins\node_modules\tabby-agentdeck` → 이 폴더로 junction 을 건다.
43
+ 소스를 고치고 `npm run build` 만 하면 Tabby 재시작으로 반영된다.
44
+ 2. `-Shortcut` 을 주면 바탕화면에 **관리자 권한** 바로가기를 만든다.
45
+ Tabby 를 관리자로 띄우면 그 안의 모든 탭이 권한을 물려받으므로, 탭마다 UAC 창이 뜨는 것을 피할 수 있다.
46
+
47
+ ## 작업 이름과 상태
48
+
49
+ 사이드바 한 줄은 **작업 이름(라벨)** 과 **상태 배지** 로 이루어지고, 둘은 채우는 경로가 다르다.
50
+ 둘 다 **플러그인 코드만으로 돈다 — 설정도, 에이전트의 협조도 필요 없다.**
51
+
52
+ ### 작업 이름 — Enter 를 누르는 순간 입력창을 읽는다
53
+
54
+ Enter 로 프롬프트를 보내는 그 순간, 화면에 그려져 있는 입력창(`❯ ...` 또는 `> ...`)을 읽어 40자로 잘라
55
+ 라벨에 넣는다 (`enterAsLabel`, 판정은 `src/prompt.ts`).
56
+
57
+ - **키 입력이 아니라 화면을 읽는다.** 붙여넣기·↑ 히스토리·자동완성·IME 로 들어온 글자는 키 이벤트로
58
+ 안 오거나 원문과 다르다. 화면에 실제로 그려진 것이 곧 보낼 내용이다.
59
+ - **Enter 만 잡는다.** Shift+Enter / Ctrl+Enter 는 줄바꿈이라 아직 전송이 아니고, 그때 라벨을 바꾸면
60
+ 쓰다 만 문장이 박힌다. IME 조합 확정용 Enter 도 제외한다. 이벤트는 관측만 하고 막지 않는다.
61
+ - **머리글자는 앱·버전마다 다르다.** Claude Code 2.1.x 는 테두리 상자 대신 가로선 두 줄 사이에
62
+ `❯`(U+276F) 한 줄을 그린다. `>`(ASCII) 만 찾으면 라벨이 한 번도 안 바뀐다 — `src/prompt.ts` 는
63
+ `> ❯ › ❭ ➜` 를 모두 받고, 같은 꺾쇠를 쓰는 선택지 커서(`❯ 1. Yes`, `❯ No, exit`)는 배제한다.
64
+ - 입력창 머리를 못 찾으면(일반 셸, y/n 확인 등) 라벨을 그대로 둔다 — 엉뚱한 문자열이 박히느니 낫다.
65
+ - `labelAsTitle`(기본 true)이면 사이드바 제목 줄에 탭 제목 대신 이 라벨이 뜬다.
66
+
67
+ > **왜 탭 제목을 쓰지 않나.** Claude Code 는 세션의 첫 작업을 요약해 콘솔 제목에 한 번 박고 그 뒤로
68
+ > 갱신하지 않는다(2026-08-28 실측 — 작업이 네 번 바뀌는 동안 제목은 `◑ sendInput 한 줄 vs 두 줄 원인 파악`
69
+ > 그대로였다). 제목만 보여주면 사이드바가 옛 작업 이름에, 셸이 붙인 엉뚱한 문자열에 박혀 있게 된다.
70
+
71
+ > **왜 에이전트 훅을 쓰지 않나.** 훅으로 받으면 쓰는 사람이 `~/.claude/settings.json` 을 손봐야 하고
72
+ > 플러그인이 그걸 대신 깔아줄 수도 없다. 게다가 훅 경로는 에이전트마다 규약이 다르다.
73
+ > 설정이 하나도 없는 상태에서 도는 경로를 기본으로 둔다.
74
+
75
+ ### 상태 — 출력과 화면 패턴으로 추론한다
76
+
77
+ PTY 출력이 흐르면 `진행중`, `idleAfterMs`(기본 2.5초) 동안 조용하면 `대기` 로 돌아간다.
78
+ `Do you want to`, `❯ 1. Yes`, `(y/n)` 같은 패턴이 보이면 `승인대기`.
79
+ Claude Code 가 제목에 넣는 스피너(`◐ ✳ ...`)도 진행중으로 읽는다.
80
+
81
+ ### 직접 지정하고 싶을 때
82
+
83
+ - 사이드바에서 **우클릭 → 상태 고르기**, **더블클릭 → 이름 편집**.
84
+ - 셸 스크립트 안에서라면 콘솔 제목을 `[AD:<status>] <라벨>` 로 바꾼다. 접두사는 표시에서 지워진다.
85
+
86
+ ```powershell
87
+ $host.UI.RawUI.WindowTitle = "[AD:waiting] 배포 승인 대기"
88
+ powershell -NoProfile -File hooksgentdeck-status.ps1 -Status running -Label "결제 버그 수정"
89
+ ```
90
+
91
+ status 는 `running` / `waiting` / `done` / `error` / `idle`.
92
+ 이렇게 지정한 상태는 **고정**되어 자동 감지가 덮어쓰지 않는다(배지에 점선 밑줄).
93
+ 우클릭 → `↺ 자동 감지로` 로 해제한다.
94
+
95
+ 이 경로는 **셸 안에서 직접 실행할 때만** 통한다. 에이전트의 훅에서 부르면 안 된다 —
96
+ 훅은 파이프 stdio 로 떠서 터미널 콘솔에 붙어 있지 않으므로 제목이 pty 까지 오지 않는다(2026-08-28 실측).
97
+
98
+ ## 키
99
+
100
+ | 키 | 동작 |
101
+ |---|---|
102
+ | `Ctrl+Enter` / `Shift+Enter` | 줄바꿈 — Claude Code 가 줄바꿈으로 받는 `0x0A`(= `Ctrl+J`) 를 보낸다. `agentdeck-newline` 핫키 |
103
+ | 우클릭 | 선택이 있으면 복사, 없으면 붙여넣기. 길게(기본 250ms) 누르면 컨텍스트 메뉴 |
104
+ | `Ctrl+V` | 붙여넣기. **클립보드에 이미지만 있으면 리터럴 `0x16`(Ctrl+V)을 그대로 흘려보낸다** — Claude Code 도 Codex 도 이 키로 클립보드를 직접 읽는다. `agentdeck-paste` 핫키 |
105
+ | (미배정) | `agentdeck-toggle` — 사이드바/4:3 즉시 on-off |
106
+
107
+ > `Ctrl+V` 는 플러그인이 기동할 때 `agentdeck-paste` 에 자동으로 넣는다(`claimCtrlV`).
108
+ > 동시에 Tabby 순정 `hotkeys.paste` 에서는 `Ctrl-V` 를 **뺀다** — 둘 다 걸려 있으면 한 번 눌러 두 번 붙는다.
109
+ >
110
+ > **왜 이미지는 `Ctrl+V` 가 아니라 `Alt+V` 였나**: 터미널의 붙여넣기는 "클립보드 *텍스트* 를 키 입력처럼
111
+ > PTY 에 써넣는" 동작이다. PTY 는 바이트 스트림이라 이미지가 지나갈 길이 없고, 터미널 안에서 도는 앱은
112
+ > OS 클립보드에 손댈 수 없다. Claude Code 는 자기가 클립보드를 직접 읽어서 이 문제를 푸는데,
113
+ > `Alt+V` 가 그 "지금 클립보드를 읽어라" 신호다. 즉 두 키는 *붙여넣는 주체* 가 다르다
114
+ > (`Ctrl+V` = 터미널이 넣어줌 / `Alt+V` = 앱이 직접 읽음). 사용자가 그걸 구분할 이유는 없으므로
115
+ > agentdeck 이 클립보드를 미리 보고 갈라준다. 텍스트가 같이 들어 있으면(엑셀·워드 복사 등) 텍스트를 우선한다.
116
+ > config.yaml 을 손으로 고쳐도 되지만, Tabby 는 종료할 때 메모리에 있던 설정을 다시 써서 손댄 값을 되돌린다.
117
+ > 대가로 터미널 앱에 리터럴 `Ctrl+V`(0x16, quoted-insert)를 보낼 수 없다 — 필요하면 `claimCtrlV: false`.
118
+
119
+ ## 설정
120
+
121
+ Tabby 설정 파일(`%APPDATA%\tabby\config.yaml`)의 `agentDeck` 아래에서 조절한다.
122
+
123
+ | 키 | 기본값 | 설명 |
124
+ |---|---|---|
125
+ | `enabled` | `true` | 사이드바 + 4:3 레이아웃 on/off |
126
+ | `aspectW` / `aspectH` | `4` / `3` | 터미널 뷰포트 비율 |
127
+ | `sidebarMin` / `sidebarMax` | `200` / `560` | 사이드바 폭 범위(px) |
128
+ | `idleAfterMs` | `2500` | 이 시간 동안 출력이 없으면 `대기` 로 |
129
+ | `autoDetect` | `true` | 출력·제목 패턴으로 상태 추론 |
130
+ | `showElapsed` | `true` | 경과 시간 표시 |
131
+ | `keepRenderingWhenHidden` | `true` | 창이 가려져 있어도 사이드바를 갱신한다. 끄면 Chromium 이 rAF·타이머를 스로틀해 상태 변화가 최대 1분까지 늦게 보인다 |
132
+ | `enterAsLabel` | `true` | Enter 로 프롬프트를 보낼 때 입력창 원문을 작업 라벨로 삼는다. 이게 라벨의 기본 경로다 |
133
+ | `notifyChannel` | `false` | 외부 통보 채널(TCP + 상태 파일 폴링)을 연다. 라벨·상태는 코드가 스스로 채우므로 기본은 꺼 둔다 |
134
+ | `labelAsTitle` | `true` | 사이드바 제목 줄에 탭 제목 대신 작업 라벨을 쓴다. 라벨이 없는 탭은 제목 그대로. Tabby 의 탭 제목 자체는 건드리지 않는다 |
135
+ | `claimCtrlV` | `true` | 기동 시 `agentdeck-paste` 에 `Ctrl-V` 를 보정해 넣고, 순정 `hotkeys.paste` 에서는 뺀다 |
136
+ | `pasteImageWithCtrlV` | `true` | 클립보드에 이미지만 있으면 텍스트 붙여넣기 대신 이미지 붙여넣기 키를 흘려보낸다 |
137
+ | `imagePasteKey` | `alt-v` | 그때 보낼 키. `ctrl-v`(0x16) 는 Claude Code·Codex 둘 다 동작한다. `alt-v`(ESC v) 는 Claude Code 전용 폴백 |
138
+ | `claimRightClick` | `true` | 우클릭을 플러그인이 직접 처리한다(Tabby 의 `terminal.rightClick` 은 `off` 로 내린다). Tabby 리스너가 겹쳐 붙어 붙여넣기가 2중으로 들어가던 문제 회피 |
139
+ | `rightClickMenuMs` | `250` | 이 시간 이상 누르고 있으면 붙여넣기 대신 컨텍스트 메뉴 |
140
+ | `terminalOpacity` | `75` | 터미널 배경 불투명도(%). 100 미만이면 컬러스킴 배경색에 알파를 넣어 `tabby-background` 의 배경 이미지가 비친다. 100 = 순정 복구 |
141
+ | `sidebarOpacity` | `75` | 사이드바 / 창 여백 불투명도(%). 같은 목적 |
142
+ | `redrawKickMs` | `900` | 탭이 열리고 이 시간 뒤 pty 폭을 한 번 흔들어 TUI 첫 화면 잔상을 지운다. 0 이면 끔 |
143
+ | `rootProfileCwd` | `<작업 루트 경로>` | 새 탭이 열릴 경로 |
144
+ | `rootProfileArgs` | `-NoLogo -ExecutionPolicy Bypass` | 셸 인자 |
145
+ | `rootProfileAdmin` | `false` | `true` 면 탭마다 UAC 승격 |
146
+
147
+ `agentdeck-toggle` 핫키에 키를 배정하면 사이드바/4:3 을 즉시 껐다 켤 수 있다(Settings → Hotkeys).
148
+
149
+ ### 폭이 정해지는 순서
150
+
151
+ 1. 터미널 폭을 `창 높이 × 4/3` 으로 잡는다.
152
+ 2. 남는 폭을 사이드바에 주되 `sidebarMin`~`sidebarMax` 로 자른다.
153
+ 3. **그러고도 폭이 남으면 4:3 을 포기하고 터미널이 흡수한다** — 오른쪽에 빈 띠를 남기지 않기 위해서다.
154
+
155
+ 그래서 넓은 모니터에서는 실제 비율이 4:3 보다 옆으로 길어진다.
156
+ 정확히 4:3 을 원하면 `sidebarMax` 를 `창 너비 − 창 높이 × 4/3` 으로 맞춘다.
157
+ 예를 들어 2560×1440 창(터미널 높이 약 1370)이라면 `2560 − 1827 ≈ 730`.
158
+
159
+ ```yaml
160
+ agentDeck:
161
+ sidebarMax: 730
162
+ ```
163
+
164
+ ## 알려진 문제
165
+
166
+ ### Tabby 1.0.235 에서 새 탭이 열리지 않음
167
+
168
+ ```
169
+ Error: ENOENT, node_modules\node-pty\lib\conpty_console_list_agent
170
+ not found in ...\resources\app.asar
171
+ ```
172
+
173
+ Tabby 설치본의 패키징 버그다. 해당 파일은 `app.asar.unpacked` 에 있는데 node-pty 가 `app.asar` 안에서 찾는다.
174
+ 플러그인과는 무관하며, ConPTY 를 끄면 이 코드 경로를 타지 않는다.
175
+
176
+ ```yaml
177
+ # %APPDATA%\tabby\config.yaml
178
+ terminal:
179
+ useConPTY: false
180
+ ```
181
+
182
+ ### npm 전역 명령(`claude` 등)이 실행 정책에 막힘
183
+
184
+ `claude` 는 `.ps1` 래퍼라 기본 실행 정책(Restricted)에서 로드되지 않는다.
185
+ `rootProfileArgs` 의 `-ExecutionPolicy Bypass` 가 이를 해결한다 — **해당 세션에만** 적용되고 시스템 정책은 건드리지 않는다.
186
+
187
+ ## 개발
188
+
189
+ ```powershell
190
+ npm run watch # 소스 감시 빌드
191
+ ```
192
+
193
+ Tabby 를 `--remote-debugging-port=9222` 로 띄우면 CDP 로 내부 상태를 들여다볼 수 있다.
194
+ 플러그인이 `window.__agentdeck` 에 `app` / `config` / `status` / `relayout()` / `render()` 를 노출한다.
195
+
196
+ ```js
197
+ window.__agentdeck.status.debug // { attached, outputs, oscHits, titleHits }
198
+ ```
199
+
200
+ ## 구조
201
+
202
+ | 파일 | 역할 |
203
+ |---|---|
204
+ | `src/index.ts` | NgModule — provider 등록, 서비스 기동 |
205
+ | `src/deck.service.ts` | 사이드바 DOM, 4:3 레이아웃, 세션·제목 감시 |
206
+ | `src/detect.ts` | 출력·제목에서 상태를 읽어내는 규칙 |
207
+ | `src/status.service.ts` | 탭별 상태 보관 |
208
+ | `src/decorator.ts` | Tabby 공식 확장점(TerminalDecorator) 경유 출력 구독 |
209
+ | `src/profile.service.ts` | 작업 루트 프로필 등록 |
210
+ | `src/notify.service.ts` | Claude Code 훅이 남긴 상태 파일을 읽어 탭에 반영 |
211
+ | `src/config.ts` | 기본 설정값 |
212
+ | `src/hotkeys.ts` | 핫키 선언 |
213
+
214
+ ### 구현하며 부딪힌 것들
215
+
216
+ - **탭은 래퍼에 싸여 있다.** `app.tabs` 의 원소는 `SplitTabComponent` 이고 터미널은 그 자식이다.
217
+ 상태 키를 정규화하지 않으면 자식에 쓰고 부모를 읽어 영영 매칭되지 않는다.
218
+ - **래퍼가 자식보다 먼저 목록에 들어온다.** 한 번 훑고 "감시 완료" 로 표시하면 빈 래퍼만 잡고 끝난다.
219
+ pane 은 호출될 때마다 다시 훑는다.
220
+ - **`HotkeysService` 는 `HotkeyProvider` 들을 주입받는다.** provider 생성자에서 그것을 다시 주입하면
221
+ 순환 의존으로 부분 초기화된 인스턴스가 넘어온다(`Cannot read properties of undefined`). 핫키 구독은 다른 서비스에서.
222
+ - **CSS 로만 폭을 바꾸면 pty 가 따라오지 않을 수 있다.** Tabby 의 xterm 은 `attach()` **맨 끝**에서야
223
+ `ResizeObserver` 를 건다(`tabby-terminal/dist/index.js:40979`). 그 앞에는 `await` 가 여러 개 있어서,
224
+ 탭이 붙는 도중(`tabOpened$`/`tabsChanged$`)에 `.content.main` 폭을 바꾸면 그 변경이 아무에게도 관측되지 않고
225
+ 유실된다. 결과: xterm 은 넓은데 pty 는 옛 폭 그대로 → Claude Code 같은 전체화면 TUI 가 화면보다 좁게 그리고
226
+ 오른쪽에 지워지지 않은 옛 프레임이 박혀 있다. 창을 복원→최대화로 흔들면 OS 리사이즈 경로를 타서 즉시 복구된다
227
+ (2026-08-28 실측). 그래서 `relayout()` 은 폭이 바뀔 때마다, 그리고 `frontendReady$` 때
228
+ `frontend.fitAddon.fit()` 을 직접 호출한다. 숨겨진 탭은 컨테이너 폭이 0 이라 건너뛴다.
229
+
230
+ - **입력창 머리글자를 `>` 로 가정한 것이 라벨이 안 바뀌던 진짜 원인이다.** 앞서 "화면 아래 14줄만
231
+ 읽어서" 로 진단해 스캔 기준을 커서 줄로 옮겼는데도 증상이 그대로였다. 실제 원인은 정규식 —
232
+ Claude Code 2.1.252 의 입력창은 `❯`(U+276F) 로 시작해서 `/^>\s*/` 에 절대 걸리지 않았다.
233
+ CDP 로 읽은 xterm 버퍼 원문이 증거다(2026-09-01):
234
+ `"────────…"`, `"❯ "`, `"────────…"`, `" ⏵⏵ auto mode on (shift+tab to cycle)"`.
235
+ 같은 실수를 반복하지 않으려면 `readPromptLine` 이 빈손일 때 남기는
236
+ `enter-label miss ... tail=` 진단 라인(커서 주변 4줄 원문)을 먼저 볼 것 —
237
+ "버퍼를 못 읽음" 과 "머리를 못 찾음" 을 이 줄 하나로 가른다.
238
+
239
+ - **`package.json` 에 `author` 가 없으면 플러그인이 조용히 로드되지 않는다.**
240
+ Tabby 의 `parsePluginInfo` 가 `author.name` 을 읽다가 예외를 삼킨다.
package/dist/api.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /** 사이드바에 표시되는 작업 상태 */
2
+ export type WorkStatus = 'idle' | 'running' | 'waiting' | 'done' | 'error';
3
+ export interface StatusStyle {
4
+ icon: string;
5
+ label: string;
6
+ color: string;
7
+ }
8
+ /** 상태별 표시 규칙 — 사이드바 배지와 좌측 컬러바에 함께 쓰인다 */
9
+ export declare const STATUS_STYLES: Record<WorkStatus, StatusStyle>;
10
+ export interface TabState {
11
+ /** 현재 상태 */
12
+ status: WorkStatus;
13
+ /** 작업 이름 (사용자 지정 또는 훅이 보낸 값) */
14
+ label: string;
15
+ /** 상태가 마지막으로 바뀐 시각 */
16
+ since: number;
17
+ /** true = 수동/훅 지정. 자동 감지가 덮어쓰지 않는다 */
18
+ pinned: boolean;
19
+ /** 마지막 PTY 출력 시각 */
20
+ lastOutput: number;
21
+ }
@@ -0,0 +1,179 @@
1
+ import { ConfigProvider } from 'tabby-core';
2
+ /** @hidden */
3
+ export declare class AgentDeckConfigProvider extends ConfigProvider {
4
+ defaults: {
5
+ agentDeck: {
6
+ /** 레이아웃/사이드바 전체 on-off */
7
+ enabled: boolean;
8
+ /** 터미널 뷰포트 가로:세로 비율 */
9
+ aspectW: number;
10
+ aspectH: number;
11
+ /**
12
+ * 사이드바 폭 제한 (px).
13
+ * sidebarMax 를 키우면 사이드바가 그만큼 넓어지고, 그래도 남는 폭은 터미널이 흡수한다
14
+ * (4:3 보다 "창을 꽉 채운다" 가 우선). config.yaml 의 agentDeck 아래에서 조절한다.
15
+ */
16
+ sidebarMin: number;
17
+ sidebarMax: number;
18
+ /**
19
+ * 훅이 상태를 밀어 넣는 TCP 채널의 포트 (127.0.0.1 전용).
20
+ *
21
+ * Windows 임시 포트 범위(49152~65535) 아래로 잡는다 — 그 위 번호는 OS 가
22
+ * 다른 프로세스에 먼저 내줄 수 있어 기동 순서에 따라 충돌한다.
23
+ * 이 번호가 물려 있으면 0(임의 포트)으로 물러나고, 실제 번호는 어느 쪽이든
24
+ * `%LOCALAPPDATA%\tabby-agentdeck\port` 에 적히므로 훅은 그 파일만 보면 된다.
25
+ */
26
+ notifyPort: number;
27
+ /**
28
+ * 외부 통보 채널(TCP + 상태 파일 폴링)을 열지.
29
+ *
30
+ * 기본은 꺼 둔다 — 라벨은 Enter 시점 입력창에서(`enterAsLabel`), 상태는 출력 패턴에서
31
+ * 플러그인이 스스로 얻으므로 바깥에서 밀어 넣어 줄 것이 없다. 켜 두면 쓰지도 않는
32
+ * 400ms 폴링과 포트 점유만 남는다. 외부 도구가 상태를 직접 보고하고 싶을 때만 켠다.
33
+ */
34
+ notifyChannel: boolean;
35
+ /** PTY 출력이 이 시간(ms) 이상 없으면 running -> idle */
36
+ idleAfterMs: number;
37
+ /** 출력 패턴으로 상태를 자동 추론할지 */
38
+ autoDetect: boolean;
39
+ /** 사이드바에 경과 시간 표시 */
40
+ showElapsed: boolean;
41
+ /**
42
+ * 창이 가려져 있어도 사이드바를 계속 갱신한다 (Electron backgroundThrottling 해제).
43
+ *
44
+ * 끄면 Chromium 기본 동작으로 돌아간다 — 창이 hidden 이면 rAF 가 멈추고 타이머가
45
+ * 1초, 5분 이상 가려지면 분당 1회까지 느려져서 훅이 보낸 done 이 화면에 늦게 뜬다.
46
+ * 켜 두면 훅 보고가 바로 보이는 대신 가려진 동안에도 렌더 비용이 조금 든다
47
+ * (사이드바 DOM 갱신뿐이라 미미하다).
48
+ */
49
+ keepRenderingWhenHidden: boolean;
50
+ /**
51
+ * 사이드바의 제목 줄에 탭 제목 대신 작업 라벨을 쓴다.
52
+ * Claude Code 는 세션 시작 때 붙인 콘솔 제목을 계속 들고 있어서 다음 작업으로
53
+ * 넘어가도 안 바뀐다(2026-08-28 실측) — 제목만 보여주면 사이드바가 옛 작업 이름에
54
+ * 박혀 있게 된다. 라벨이 없는 탭은 그대로 제목을 보여준다.
55
+ * Tabby 의 탭 제목 자체(customTitle)는 건드리지 않는다 — 앱이 넣는 제목을
56
+ * 가리지 않아야 그게 갱신되는지 계속 관측할 수 있다.
57
+ */
58
+ labelAsTitle: boolean;
59
+ /**
60
+ * Enter 로 프롬프트를 보낼 때 그 원문을 작업 라벨로 삼는다 (claimEnterLabel 주석 참고).
61
+ * 훅도 콘솔 제목도 못 믿는 배포 환경에서 라벨을 최신으로 유지하는 유일한 경로다.
62
+ * Shift+Enter / Ctrl+Enter(줄바꿈)는 잡지 않는다.
63
+ */
64
+ enterAsLabel: boolean;
65
+ /**
66
+ * Ctrl+V 를 agentdeck 이 직접 받는다 (document 캡처 단계에서 한 번만).
67
+ *
68
+ * 핫키(`agentdeck-paste`)로 받던 시절에는 Tabby 가 같은 keydown 을 두 경로로
69
+ * HotkeysService 에 밀어 넣어 한 번 눌러 두 번 붙는 일이 남아 있었다
70
+ * (deck.service.ts `ensurePasteHotkey` 주석에 원인). 캡처로 옮기고 전파를 끊으면서
71
+ * 경로가 하나로 정리됐다.
72
+ *
73
+ * false 로 두면 터미널 앱에 리터럴 Ctrl+V(0x16, quoted-insert)를 보낼 수 있다
74
+ */
75
+ claimCtrlV: boolean;
76
+ /**
77
+ * 클립보드에 이미지만 있으면 Ctrl+V 로도 이미지가 들어가게 한다.
78
+ *
79
+ * 터미널 붙여넣기는 "클립보드 텍스트를 PTY 에 써넣는" 동작이라 이미지는 지나갈 수가 없다.
80
+ * 대신 에이전트 CLI 는 자기가 OS 클립보드를 직접 읽는 키를 따로 두므로,
81
+ * 이미지일 때만 그 키를 흘려보낸다 (어떤 키인지는 `imagePasteKey`).
82
+ */
83
+ pasteImageWithCtrlV: boolean;
84
+ /**
85
+ * 이미지 전용 클립보드일 때 흘려보낼 키의 **기본값**. `'alt-v'`(ESC v) | `'ctrl-v'`(0x16).
86
+ *
87
+ * 평소에는 이 값이 쓰이지 않는다 — 탭에서 도는 앱을 보고 자동으로 갈라 보내기 때문이다
88
+ * (Claude Code = ESC v / Codex = 0x16, `deck.service.ts` `sendImagePasteKey` 주석에 실측).
89
+ * 앱을 못 알아낼 때(SSH 세션 등)의 폴백으로만 쓴다.
90
+ *
91
+ * Claude Code 는 PTY 로 들어온 0x16 에 반응하지 않는 것이 실측됐으므로
92
+ * (`.agentdeck-diag.log` 2026-08-28T09:33:47~48 에 `paste image (ctrl-v)` 5줄, 이미지 미삽입)
93
+ * 폴백은 더 널리 먹히는 ESC v 로 둔다.
94
+ */
95
+ imagePasteKey: string;
96
+ /**
97
+ * 우클릭(붙여넣기 / 선택 시 복사)을 agentdeck 이 직접 처리한다.
98
+ * Tabby 자체 처리를 끄고(terminal.rightClick = 'off') 캡처 단계에서 한 번만
99
+ * 처리하므로, 핸들러가 두 번 걸려 붙여넣기가 2중으로 들어가던 문제가 사라진다.
100
+ * false 로 두면 Tabby 순정 동작으로 되돌아간다
101
+ */
102
+ claimRightClick: boolean;
103
+ /** 우클릭을 이 시간(ms) 이상 누르고 있으면 붙여넣기 대신 컨텍스트 메뉴 */
104
+ rightClickMenuMs: number;
105
+ /**
106
+ * 터미널 배경 불투명도 (%). 100 이면 순정(완전 불투명).
107
+ *
108
+ * tabby-background 플러그인은 배경 이미지를 `.content-tab-active::before`
109
+ * (position:fixed, z-index:-1) 로 **모든 내용 뒤에** 깐다. 그런데 xterm 은
110
+ * 컬러스킴의 `background` 색으로 셀을 직접 칠하므로, 그 색에 알파가 없으면
111
+ * 이미지는 터미널 영역에서 100% 가려진다 — "배경 이미지를 넣었는데 아무 데도 안 보인다"
112
+ * 의 정체가 이것이다(2026-08-28 실측: colorScheme.background = '#171717' 불투명).
113
+ * Tabby 는 xterm 을 `allowTransparency: true` 로 만들기 때문에 알파만 넣어주면 비친다.
114
+ *
115
+ * 그래서 agentdeck 이 기동 시 `terminal.colorScheme.background` 를 8자리 hex 로 바꿔준다.
116
+ * 100 으로 되돌리면 알파를 떼어 원래 색으로 복구한다 (되돌리기 가능).
117
+ */
118
+ terminalOpacity: number;
119
+ /** 사이드바 배경 불투명도 (%). 터미널과 같은 이유로 배경 이미지가 비치게 한다 */
120
+ sidebarOpacity: number;
121
+ /**
122
+ * 새 탭에서 첫 화면이 깨질 때, 세션이 열린 뒤 이 시간(ms)에 pty 크기를 한 번 흔든다.
123
+ * 0 이면 끄기. 자세한 이유는 deck.service 의 kickPtySize 주석 참고
124
+ */
125
+ redrawKickMs: number;
126
+ /**
127
+ * 기동 후 이 시간(ms)에 **창을 복원→최대화로 한 번 흔든다**. 0 이면 끄기.
128
+ *
129
+ * pty 크기 흔들기(redrawKickMs)가 놓치는 경우를 위한 최종 보험이다. 사용자가 손으로
130
+ * 하던 바로 그 동작이고, OS 가 진짜 SIGWINCH 를 두 번 내므로 TUI 가 반드시 다시 그린다.
131
+ * 창이 최대화 상태일 때만 동작한다 — 아니면 창 상태를 멋대로 바꾸게 되므로.
132
+ */
133
+ startupWindowKickMs: number;
134
+ /**
135
+ * 작업 루트 전용 프로필을 만들고 기본 프로필로 삼는다.
136
+ *
137
+ * 에이전트 CLI 는 "어느 폴더에서 떴는가" 가 곧 작업 대상이라 새 탭마다 cd 하게 되는데,
138
+ * 그걸 없애려는 기능이다. 기본은 꺼져 있다 — 남의 PC 에 설치되자마자 프로필을 만들고
139
+ * 기본값을 가로채면 곤란하므로, 쓰려는 사람이 `rootProfileCwd` 를 채우고 켜야 한다.
140
+ */
141
+ rootProfile: boolean;
142
+ rootProfileName: string;
143
+ rootProfileCwd: string;
144
+ rootProfileCommand: string;
145
+ /**
146
+ * -ExecutionPolicy Bypass 가 필요한 이유: npm 전역 명령(claude 등)은 .ps1 래퍼인데
147
+ * 기본 실행 정책(Restricted)에서는 로드가 막힌다. 이 인자는 해당 세션에만 적용되고
148
+ * 시스템/사용자 정책은 건드리지 않는다.
149
+ */
150
+ rootProfileArgs: string[];
151
+ /**
152
+ * 탭에 주입할 환경변수. Claude Code 등 TUI 는 이 값들이 없으면 색 지원을 낮게 잡아
153
+ * 흑백에 가깝게 렌더된다 (Windows 는 TERM 이 기본으로 비어 있다).
154
+ */
155
+ rootProfileEnv: {
156
+ TERM: string;
157
+ COLORTERM: string;
158
+ FORCE_COLOR: string;
159
+ };
160
+ /** true 면 탭마다 UAC 승격 (Tabby 자체를 관리자로 띄웠다면 불필요) */
161
+ rootProfileAdmin: boolean;
162
+ /** 기본 프로필 지정을 이미 했는지 — 사용자 선택을 매번 덮어쓰지 않기 위한 플래그 */
163
+ rootProfileClaimed: boolean;
164
+ };
165
+ hotkeys: {
166
+ 'agentdeck-toggle': any[];
167
+ /**
168
+ * Claude Code 는 0x0A(Ctrl+J)를 줄바꿈으로 받는다. Tabby 로 옮겨 왔으니
169
+ * Ctrl+Enter / Shift+Enter 로도 같은 바이트를 보낸다
170
+ */
171
+ 'agentdeck-newline': string[];
172
+ /**
173
+ * Tabby 순정 `paste` 대신 이걸 쓴다 — 클립보드가 이미지인지 텍스트인지 보고
174
+ * 갈라야 하는데 순정 paste 는 텍스트만 알기 때문
175
+ */
176
+ 'agentdeck-paste': string[];
177
+ };
178
+ };
179
+ }