tabby-agentdeck 0.1.0 → 0.2.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/README.md +178 -240
- package/dist/api.d.ts +10 -0
- package/dist/claudeHooks.d.ts +22 -0
- package/dist/config.d.ts +156 -34
- package/dist/deck.service.d.ts +140 -29
- package/dist/dock.d.ts +62 -0
- package/dist/index.js +2319 -316
- package/dist/index.js.map +1 -1
- package/dist/notify.service.d.ts +19 -6
- package/dist/screen.d.ts +42 -0
- package/dist/settings.component.d.ts +52 -0
- package/dist/settings.d.ts +11 -0
- package/dist/status.service.d.ts +9 -0
- package/hooks/agentdeck-notify.ps1 +22 -23
- package/install.ps1 +60 -2
- package/package.json +50 -50
package/dist/notify.service.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { AppService, ConfigService } from 'tabby-core';
|
|
1
|
+
import { AppService, ConfigService, PlatformService } from 'tabby-core';
|
|
2
2
|
import { WorkStatusService } from './status.service';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* 에이전트 훅 -> 사이드바 상태 채널. **기본으로 켜져 있다** (`notifyChannel`).
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* 이쪽이 상태의 정확한 원천이다 — 에이전트가 "지시를 받았다 / 승인을 기다린다 / 끝났다" 를
|
|
7
|
+
* 직접 알려준다. 출력 패턴으로 추측하는 경로(detect.ts)는 훅이 없는 환경을 위한 폴백이고,
|
|
8
|
+
* TUI 가 화면을 어떻게 그리는지에 기대는 만큼 흔들린다 (2026-09-01: Claude Code 가 놀 때도
|
|
9
|
+
* 상태줄을 매초 갱신해 진행중이 안 풀렸다).
|
|
10
|
+
*
|
|
11
|
+
* 훅은 Claude Code 쪽 설정에 있어야 해서 플러그인 설치만으로는 걸리지 않는다. 그래서
|
|
12
|
+
* 기동할 때 한 번 물어보고(promptHooksOnce), 설정 창에도 설치 버튼을 둔다(claudeHooks.ts).
|
|
9
13
|
*
|
|
10
14
|
* 왜 파일인가 — 콘솔 제목 방식(`hooks/agentdeck-status.ps1`)은 **훅에서는 통하지 않는다**.
|
|
11
15
|
* Claude Code 는 훅을 파이프 stdio 로 띄우기 때문에 그 자식 프로세스는 터미널의 콘솔에
|
|
@@ -32,6 +36,7 @@ export declare class WorkNotifyService {
|
|
|
32
36
|
private app;
|
|
33
37
|
private config;
|
|
34
38
|
private status;
|
|
39
|
+
private platform;
|
|
35
40
|
private root;
|
|
36
41
|
private dir;
|
|
37
42
|
/** 훅이 접속할 포트를 알려주는 파일 */
|
|
@@ -45,7 +50,15 @@ export declare class WorkNotifyService {
|
|
|
45
50
|
private timer;
|
|
46
51
|
/** 폴링을 시작한 시각 — 이보다 오래된 파일은 지난 실행의 잔재다 */
|
|
47
52
|
private startedAt;
|
|
48
|
-
constructor(app: AppService, config: ConfigService, status: WorkStatusService);
|
|
53
|
+
constructor(app: AppService, config: ConfigService, status: WorkStatusService, platform: PlatformService);
|
|
54
|
+
/**
|
|
55
|
+
* 훅이 안 걸려 있으면 기동할 때 한 번 물어본다.
|
|
56
|
+
*
|
|
57
|
+
* 훅은 Claude Code 쪽 설정(`~/.claude/settings.json`)에 있어야 해서 플러그인을 설치하는 것만으로는
|
|
58
|
+
* 걸리지 않는다. 그렇다고 말없이 남의 설정을 고칠 수는 없으니, 한 번 묻고 고른 대로 한다.
|
|
59
|
+
* "다시 묻지 않기" 를 고르면 그 뒤로는 조용하고, 설정 창의 설치 버튼은 언제든 그대로 쓸 수 있다.
|
|
60
|
+
*/
|
|
61
|
+
private promptHooksOnce;
|
|
49
62
|
init(): void;
|
|
50
63
|
/** 진단용 */
|
|
51
64
|
get debugDir(): string;
|
package/dist/screen.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 터미널 화면이 깨졌는지 판정한다.
|
|
3
|
+
*
|
|
4
|
+
* "깨졌다" 의 정체는 하나다 — **TUI 가 아는 폭과 xterm 이 가진 폭이 다르다.**
|
|
5
|
+
* 그러면 TUI 가 그린 한 행이 xterm 에서 두 행으로 접히거나(폭이 더 크다),
|
|
6
|
+
* 행 오른쪽에 옛 프레임이 남는다(폭이 더 작다). 눈으로는 "입력창 구분선이 중간에서 끊긴다",
|
|
7
|
+
* "가로선과 입력 텍스트가 한 줄에 섞인다" 로 보인다.
|
|
8
|
+
*
|
|
9
|
+
* 정상 화면의 모양은 실측해서 확정했다 (2026-09-01, Claude Code 2.1.x, CDP 로 xterm 버퍼 원문).
|
|
10
|
+
* 사이드바를 켠 창(cols=280)과 순정 창(cols=360) 양쪽에서 완전히 같은 구조가 나왔다.
|
|
11
|
+
*
|
|
12
|
+
* 71 wrapped=0 len=278 " … ● high · /effort" ← 오른쪽 정렬 힌트
|
|
13
|
+
* 72 wrapped=0 len=280 "──────────────────────────────────────────────────" ← len == cols
|
|
14
|
+
* 73 wrapped=1 len= 1 "❯" ← wrapped=1 이 정상
|
|
15
|
+
* 74 wrapped=0 len=280 "──────────────────────────────────────────────────" ← len == cols
|
|
16
|
+
* 75 wrapped=0 len=155 " ⚠ Transcript saving is off — …"
|
|
17
|
+
* 76 wrapped=0 len= 58 " ⏱ 1s 컨텍스트 사용량: …"
|
|
18
|
+
* 77 wrapped=0 len= 38 " ⏵⏵ auto mode on (shift+tab to cycle)"
|
|
19
|
+
*
|
|
20
|
+
* 여기서 놓치기 쉬운 세 가지 —
|
|
21
|
+
* - 가로선은 **cols 를 정확히 꽉 채운다**. 그래서 그 다음 줄에 `wrapped=1` 이 붙는 것은
|
|
22
|
+
* xterm 의 pending-wrap 이고 **정상이다**. `wrapped` 만 보고 깨졌다고 판정하면 안 된다.
|
|
23
|
+
* - 프롬프트 오른쪽에 오는 힌트(`● high · /effort`, `Try "how does …"`)는 **별도 줄**이다.
|
|
24
|
+
* 프롬프트 줄과 같은 줄에 두 개의 머리(`❯`/`>`)가 보이면 그것이 깨진 화면이다.
|
|
25
|
+
* - **폭이 다 맞아도 깨진 화면이 있다.** 테두리 두 줄이 맞붙어 `❯` 가 들어갈 행이
|
|
26
|
+
* 사라지는 모양인데, 두 테두리 모두 cols 를 꽉 채우므로 폭 기준 판정은 전부 통과한다
|
|
27
|
+
* (2026-09-02: 사람 눈으로도 이 도구로도 "정상" 이라고 오판했다). 그래서 폭만이 아니라
|
|
28
|
+
* **입력할 자리가 있는가**(테두리 사이 간격 ≥ 2)를 같이 본다.
|
|
29
|
+
*
|
|
30
|
+
* xterm 없이 검증할 수 있도록 줄 배열만 받는 순수 함수로 둔다.
|
|
31
|
+
*/
|
|
32
|
+
/** 판정에 필요한 한 줄 — 오른쪽 공백을 떼어낸 본문과 xterm 의 wrap 플래그 */
|
|
33
|
+
export interface ScreenLine {
|
|
34
|
+
text: string;
|
|
35
|
+
wrapped: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface ScreenVerdict {
|
|
38
|
+
broken: boolean;
|
|
39
|
+
/** 왜 깨졌다고 보는지 — 진단 로그에 그대로 남긴다 */
|
|
40
|
+
reasons: string[];
|
|
41
|
+
}
|
|
42
|
+
export declare function judgeScreen(lines: ScreenLine[], cols: number): ScreenVerdict;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ConfigService, PlatformService } from 'tabby-core';
|
|
2
|
+
/**
|
|
3
|
+
* 설정 > AgentDeck 탭.
|
|
4
|
+
*
|
|
5
|
+
* 사용자가 실제로 바꿀 일이 있는 두 가지만 노출한다 — 레이아웃 on/off 와 작업 루트 프로필.
|
|
6
|
+
* 나머지(화면비, 사이드바 폭, 투명도, 상태 감지, 입력 가로채기, 렌더 보정, 통보 채널)는
|
|
7
|
+
* 손댈 이유가 거의 없고 잘못 만지면 화면이 깨지는 값이라 `config.ts` 의 기본값으로 고정하고
|
|
8
|
+
* 설정 창에서는 뺐다. 정말 필요하면 설정 파일의 `agentDeck.*` 를 직접 고친다.
|
|
9
|
+
*
|
|
10
|
+
* `enabled` 는 deck.service 가 `config.changed$` 를 구독해 즉시 반영한다(deck.service.ts:256).
|
|
11
|
+
* 루트 프로필은 기동 시 한 번만 읽으므로 재시작이 필요하다고 적어 둔다.
|
|
12
|
+
*/
|
|
13
|
+
export declare class AgentDeckSettingsTabComponent {
|
|
14
|
+
config: ConfigService;
|
|
15
|
+
private platform;
|
|
16
|
+
/** 이 환경에서 훅을 걸 수 있나 (통보 스크립트가 PowerShell 이라 Windows 전용) */
|
|
17
|
+
hooksSupported: boolean;
|
|
18
|
+
/** 지금 걸려 있나 — 파일을 매번 읽지 않도록 화면용으로 들고 있는다 */
|
|
19
|
+
hooksOn: boolean;
|
|
20
|
+
/** 훅을 쓰다 실패한 이유 — 조용히 삼키면 사용자는 버튼이 먹통인 줄 안다 */
|
|
21
|
+
hookError: string;
|
|
22
|
+
constructor(config: ConfigService, platform: PlatformService);
|
|
23
|
+
private refreshHooks;
|
|
24
|
+
/**
|
|
25
|
+
* 훅을 걸거나 뗀다.
|
|
26
|
+
*
|
|
27
|
+
* 걸 때는 통보 채널도 같이 켠다 — 훅만 걸고 채널이 닫혀 있으면 보고가 어디에도 닿지 않는데,
|
|
28
|
+
* 사용자 입장에서는 "설치했는데 아무 일도 안 일어난다" 로만 보인다.
|
|
29
|
+
*/
|
|
30
|
+
toggleHooks(): void;
|
|
31
|
+
/**
|
|
32
|
+
* OS 폴더 선택 창을 띄워 작업 폴더를 고른다.
|
|
33
|
+
*
|
|
34
|
+
* `pickDirectory` 는 취소하면 null/undefined 를 주므로 그때는 기존 값을 건드리지 않는다.
|
|
35
|
+
* 다이얼로그 제목은 Tabby 버전에 따라 인자를 받기도 하고 안 받기도 해서 any 로 넘긴다 —
|
|
36
|
+
* 안 받는 쪽은 그냥 무시하므로 어느 쪽에서도 깨지지 않는다.
|
|
37
|
+
* 돌려받은 경로는 백슬래시를 슬래시로 바꿔 저장한다. 설정 파일에 그대로 적히는 값이라
|
|
38
|
+
* 손으로 고칠 때 이스케이프 사고가 나지 않게 하려는 것이고, Windows 도 슬래시 경로를 받는다.
|
|
39
|
+
*/
|
|
40
|
+
/** 배치가 기본값에서 벗어나 있나 — 벗어나 있을 때만 초기화 버튼이 살아 있다 */
|
|
41
|
+
layoutChanged(): boolean;
|
|
42
|
+
dockLabel(): string;
|
|
43
|
+
sizeLabel(): string;
|
|
44
|
+
/**
|
|
45
|
+
* 도킹 방향과 크기만 기본값으로 되돌린다.
|
|
46
|
+
*
|
|
47
|
+
* 되돌리기 쉬운 조작이라 확인 창은 두지 않는다. `config.save()` 가 `changed$` 를 발화시키고
|
|
48
|
+
* deck.service 가 그걸 받아 곧바로 다시 배치하므로(deck.service.ts:256) 재시작도 필요 없다.
|
|
49
|
+
*/
|
|
50
|
+
resetLayout(): void;
|
|
51
|
+
pickCwd(): Promise<void>;
|
|
52
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SettingsTabProvider } from 'tabby-settings';
|
|
2
|
+
/**
|
|
3
|
+
* 설정 창 왼쪽 목록에 AgentDeck 탭을 끼워 넣는다.
|
|
4
|
+
* `weight` 를 주지 않으면 등록 순서 뒤쪽에 붙는다 — 순정 탭들 아래로 가도록 그대로 둔다.
|
|
5
|
+
*/
|
|
6
|
+
export declare class AgentDeckSettingsTabProvider extends SettingsTabProvider {
|
|
7
|
+
id: string;
|
|
8
|
+
icon: string;
|
|
9
|
+
title: string;
|
|
10
|
+
getComponentType(): any;
|
|
11
|
+
}
|
package/dist/status.service.d.ts
CHANGED
|
@@ -26,6 +26,15 @@ export declare class WorkStatusService {
|
|
|
26
26
|
/** 수동 고정을 풀고 자동 감지로 되돌린다 */
|
|
27
27
|
unpin(tab: BaseTabComponent): void;
|
|
28
28
|
markOutput(tab: BaseTabComponent): void;
|
|
29
|
+
/**
|
|
30
|
+
* 에이전트가 "작업 중" 이라고 스스로 밝힌 순간 — 스피너나 "esc to interrupt" 같은 표시.
|
|
31
|
+
*
|
|
32
|
+
* 이 신호를 한 번이라도 본 탭은 그 뒤로 신호만 보고 판단한다(busyMode).
|
|
33
|
+
* 상태줄을 매초 다시 그리는 TUI 를 "계속 일하는 중" 으로 오해하지 않기 위해서다.
|
|
34
|
+
*/
|
|
35
|
+
markBusy(tab: BaseTabComponent): void;
|
|
36
|
+
/** 이 탭을 에이전트 TUI 로 볼지 — 아니면 예전처럼 출력 유무로 판단한다 */
|
|
37
|
+
isBusyMode(tab: BaseTabComponent): boolean;
|
|
29
38
|
forget(tab: BaseTabComponent): void;
|
|
30
39
|
/** 사이드바가 매 틱 호출 — idle 전환만 담당하고 화면 갱신 필요 여부를 돌려준다 */
|
|
31
40
|
tickIdle(tab: BaseTabComponent, idleAfterMs: number): boolean;
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
Claude Code 가 훅을 파이프 stdio 로 띄우므로 그 프로세스는 터미널 콘솔에 붙어
|
|
11
11
|
있지 않고, 제목을 바꿔도 pty 까지 전달되지 않는다 (2026-08-28 실측).
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
이 훅이 보내는 것은 **상태뿐이다.** 작업 이름(라벨)은 플러그인이 Enter 시점에
|
|
14
|
+
입력창을 직접 읽어 무조건 갱신하므로(enterAsLabel) 여기서 건드리면 되돌리기만 한다.
|
|
15
|
+
예외는 사람이 셸에서 `-Label` 로 직접 지정할 때뿐.
|
|
15
16
|
|
|
16
17
|
stdout 으로 아무것도 내보내지 않는다 — UserPromptSubmit 훅의 stdout 은
|
|
17
18
|
프롬프트 컨텍스트로 주입되기 때문이다.
|
|
@@ -28,13 +29,15 @@ param(
|
|
|
28
29
|
[ValidateSet('running', 'waiting', 'done', 'error', 'idle')]
|
|
29
30
|
[string]$Status,
|
|
30
31
|
|
|
31
|
-
#
|
|
32
|
+
# 사람이 셸에서 직접 붙이는 작업 이름. 이때만 라벨을 보낸다
|
|
32
33
|
[string]$Label,
|
|
33
34
|
|
|
34
35
|
# stdin 훅 JSON 없이 부를 때 대상 세션 (없으면 가장 최근에 갱신된 세션)
|
|
35
36
|
[string]$SessionId,
|
|
36
37
|
|
|
37
|
-
#
|
|
38
|
+
# [폐기] 프롬프트를 라벨로 쓰던 스위치. 지금은 무시한다 — 라벨은 플러그인 전담.
|
|
39
|
+
# 파라미터 자체는 남겨 둔다: 이미 설치된 settings.json 이 이 인자를 들고 있으면
|
|
40
|
+
# 없앤 순간 "알 수 없는 인자" 로 훅이 통째로 실패한다
|
|
38
41
|
[switch]$PromptAsLabel,
|
|
39
42
|
|
|
40
43
|
[int]$MaxLabel = 40
|
|
@@ -72,14 +75,12 @@ if (-not $targetId) { $targetId = 'default' }
|
|
|
72
75
|
$safeId = ($targetId -replace '[^A-Za-z0-9._-]', '_')
|
|
73
76
|
$file = Join-Path $dir "$safeId.json"
|
|
74
77
|
|
|
75
|
-
# 직전 상태를 읽어 둔다 —
|
|
78
|
+
# 직전 상태를 읽어 둔다 — 승인대기 오탐 방지에 쓴다
|
|
76
79
|
$prevStatus = ''
|
|
77
|
-
$prevLabel = ''
|
|
78
80
|
if (Test-Path $file) {
|
|
79
81
|
try {
|
|
80
82
|
$prev = Get-Content -Raw -Encoding UTF8 $file | ConvertFrom-Json
|
|
81
83
|
$prevStatus = [string]$prev.status
|
|
82
|
-
$prevLabel = [string]$prev.label
|
|
83
84
|
} catch { }
|
|
84
85
|
}
|
|
85
86
|
|
|
@@ -96,20 +97,19 @@ if ($Status -eq 'waiting') {
|
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
# --- 라벨 결정 ---
|
|
99
|
-
#
|
|
100
|
+
# **이 훅은 라벨을 만들지 않는다.** 작업 이름은 플러그인이 Enter 시점에 입력창을 읽어
|
|
101
|
+
# 무조건 갱신하는 경로(claimEnterLabel / enterAsLabel)가 전담한다. 훅이 낼 수 있는 건
|
|
102
|
+
# 어차피 프롬프트 원문뿐인데, 그건 플러그인이 화면에서 더 정확하게 이미 읽고 있다.
|
|
100
103
|
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if ($null -eq $newLabel) { $newLabel = '' }
|
|
111
|
-
}
|
|
112
|
-
$newLabel = ($newLabel -replace '\s+', ' ').Trim()
|
|
104
|
+
# 훅이 라벨에 손대면 오히려 되돌린다 — 훅은 자기 상태 파일에서 직전 라벨을 읽어 다시 쓰므로,
|
|
105
|
+
# 플러그인이 방금 넣은 최신 프롬프트가 옛 값으로 덮인다
|
|
106
|
+
# (2026-09-01 실측: diag 에 07:42 프롬프트가 찍혔는데 status\c73e151f….json 은 16:44 에
|
|
107
|
+
# 07:11 의 첫 프롬프트로 재기록돼 사이드바가 세션 첫 문장에 박혀 있었다).
|
|
108
|
+
#
|
|
109
|
+
# 그래서 payload 에 label 을 넣는 경우는 하나뿐이다 — 셸에서 사람이 `-Label` 로 직접 지정할 때.
|
|
110
|
+
# 그 외에는 키 자체를 빼서 플러그인이 "라벨은 건드리지 말라" 로 읽게 한다
|
|
111
|
+
# (notify.service 는 label 이 없거나 비면 지금 값을 유지한다).
|
|
112
|
+
$newLabel = ($Label -replace '\s+', ' ').Trim()
|
|
113
113
|
if ($newLabel.Length -gt $MaxLabel) { $newLabel = $newLabel.Substring(0, $MaxLabel).TrimEnd() + '...' }
|
|
114
114
|
|
|
115
115
|
# -Status 없이 -Label 만 주면 상태는 그대로 두고 이름만 바꾼다
|
|
@@ -118,15 +118,14 @@ $effectiveStatus = if ($Status) { $Status } elseif ($prevStatus) { $prevStatus }
|
|
|
118
118
|
$payload = [ordered]@{
|
|
119
119
|
sessionId = $targetId
|
|
120
120
|
status = $effectiveStatus
|
|
121
|
-
label = $newLabel
|
|
122
121
|
ts = [int64](([datetime]::UtcNow - [datetime]'1970-01-01').TotalMilliseconds)
|
|
123
122
|
}
|
|
123
|
+
if ($newLabel) { $payload['label'] = $newLabel }
|
|
124
124
|
|
|
125
125
|
# BOM 없는 UTF-8 로 원자적 쓰기 — Node 의 JSON.parse 는 BOM 을 못 먹는다
|
|
126
126
|
#
|
|
127
127
|
# TCP 로 밀어 넣더라도 이 파일 쓰기는 건너뛰지 않는다 — 파일이 곧 상태 저장소라서
|
|
128
|
-
# 다음 훅 호출이 여기서 직전
|
|
129
|
-
# 그게 라벨 유지와 '승인대기 오탐 차단' 의 근거다.
|
|
128
|
+
# 다음 훅 호출이 여기서 직전 상태를 읽는다(위 $prevStatus). 그게 '승인대기 오탐 차단' 의 근거다.
|
|
130
129
|
$json = $payload | ConvertTo-Json -Compress
|
|
131
130
|
$tmp = "$file.tmp"
|
|
132
131
|
$enc = New-Object System.Text.UTF8Encoding($false)
|
package/install.ps1
CHANGED
|
@@ -12,11 +12,19 @@
|
|
|
12
12
|
Tabby 를 관리자로 띄우면 그 안에서 열리는 모든 탭이 권한을 물려받으므로
|
|
13
13
|
탭마다 UAC 창이 뜨는 것을 피할 수 있다.
|
|
14
14
|
|
|
15
|
+
.PARAMETER Hooks
|
|
16
|
+
Claude Code 설정(~/.claude/settings.json)에 상태 통보 훅을 등록한다.
|
|
17
|
+
|
|
18
|
+
이게 사이드바 상태의 가장 정확한 원천이다 — 에이전트가 "지금 시작했다 / 승인을 기다린다 /
|
|
19
|
+
끝났다" 를 직접 알려주므로, 화면에 그려진 글자를 보고 추측할 필요가 없다.
|
|
20
|
+
등록하지 않아도 동작은 하지만(출력 패턴 폴백) 판정이 그만큼 흔들린다.
|
|
21
|
+
|
|
15
22
|
.EXAMPLE
|
|
16
|
-
powershell -NoProfile -ExecutionPolicy Bypass -File install.ps1 -Shortcut
|
|
23
|
+
powershell -NoProfile -ExecutionPolicy Bypass -File install.ps1 -Shortcut -Hooks
|
|
17
24
|
#>
|
|
18
25
|
param(
|
|
19
|
-
[switch]$Shortcut
|
|
26
|
+
[switch]$Shortcut,
|
|
27
|
+
[switch]$Hooks
|
|
20
28
|
)
|
|
21
29
|
|
|
22
30
|
$ErrorActionPreference = 'Stop'
|
|
@@ -55,6 +63,56 @@ $pkg.dependencies | Add-Member -NotePropertyName 'tabby-agentdeck' -NoteProperty
|
|
|
55
63
|
$pkg | ConvertTo-Json -Depth 10 | Out-File $pkgPath -Encoding utf8
|
|
56
64
|
Write-Host "의존성 등록: $pkgPath (tabby-agentdeck = $spec)"
|
|
57
65
|
|
|
66
|
+
# 2-2) Claude Code 상태 통보 훅 등록
|
|
67
|
+
if ($Hooks) {
|
|
68
|
+
$settingsPath = Join-Path $env:USERPROFILE '.claude\settings.json'
|
|
69
|
+
$notify = Join-Path $source 'hooks\agentdeck-notify.ps1'
|
|
70
|
+
if (-not (Test-Path $notify)) {
|
|
71
|
+
throw "훅 스크립트를 찾지 못했다: $notify"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (Test-Path $settingsPath) {
|
|
75
|
+
# 남의 설정을 건드리는 일이라 되돌릴 수 있게 한 벌 남긴다
|
|
76
|
+
Copy-Item $settingsPath "$settingsPath.agentdeck-backup" -Force
|
|
77
|
+
$settings = Get-Content $settingsPath -Raw -Encoding utf8 | ConvertFrom-Json
|
|
78
|
+
} else {
|
|
79
|
+
New-Item -ItemType Directory -Force -Path (Split-Path $settingsPath) | Out-Null
|
|
80
|
+
$settings = [pscustomobject]@{}
|
|
81
|
+
}
|
|
82
|
+
if (-not $settings.hooks) {
|
|
83
|
+
$settings | Add-Member -NotePropertyName hooks -NotePropertyValue ([pscustomobject]@{}) -Force
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# 어떤 순간에 무엇을 보고할지.
|
|
87
|
+
# UserPromptSubmit — 지시를 받았다 (프롬프트 원문을 작업 이름으로 삼는다)
|
|
88
|
+
# Notification — 승인을 기다린다
|
|
89
|
+
# Stop — 답이 끝났다
|
|
90
|
+
$plan = @(
|
|
91
|
+
@{ Event = 'UserPromptSubmit'; Args = '-Status running -PromptAsLabel' },
|
|
92
|
+
@{ Event = 'Notification'; Args = '-Status waiting' },
|
|
93
|
+
@{ Event = 'Stop'; Args = '-Status done' }
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
foreach ($p in $plan) {
|
|
97
|
+
$cmd = "powershell -NoProfile -ExecutionPolicy Bypass -File `"$notify`" $($p.Args)"
|
|
98
|
+
$entry = [pscustomobject]@{
|
|
99
|
+
matcher = '*'
|
|
100
|
+
hooks = @([pscustomobject]@{ type = 'command'; command = $cmd; timeout = 5 })
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
$existing = @($settings.hooks.$($p.Event))
|
|
104
|
+
# 이미 우리 훅이 있으면 새 경로/인자로 갈아끼운다 (중복 등록 방지)
|
|
105
|
+
$kept = @($existing | Where-Object {
|
|
106
|
+
$_ -and -not ($_ | ConvertTo-Json -Depth 10 -Compress).Contains('agentdeck-notify')
|
|
107
|
+
})
|
|
108
|
+
$settings.hooks | Add-Member -NotePropertyName $p.Event -NotePropertyValue ($kept + $entry) -Force
|
|
109
|
+
Write-Host "훅 등록: $($p.Event) -> $($p.Args)"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
$settings | ConvertTo-Json -Depth 20 | Out-File $settingsPath -Encoding utf8
|
|
113
|
+
Write-Host "저장: $settingsPath (백업: $settingsPath.agentdeck-backup)"
|
|
114
|
+
}
|
|
115
|
+
|
|
58
116
|
# 3) 관리자 권한 바로가기
|
|
59
117
|
if ($Shortcut) {
|
|
60
118
|
$exe = Join-Path $env:LOCALAPPDATA 'Programs\Tabby\Tabby.exe'
|
package/package.json
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "tabby-agentdeck",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Run AI coding agents side by side in Tabby - a live deck of agent sessions with status, agent-friendly hotkeys and image paste",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"tabby-plugin",
|
|
7
|
-
"tabby",
|
|
8
|
-
"ai",
|
|
9
|
-
"agent",
|
|
10
|
-
"claude-code",
|
|
11
|
-
"codex",
|
|
12
|
-
"terminal"
|
|
13
|
-
],
|
|
14
|
-
"main": "dist/index.js",
|
|
15
|
-
"typings": "dist/index.d.ts",
|
|
16
|
-
"scripts": {
|
|
17
|
-
"build": "webpack --progress --color",
|
|
18
|
-
"watch": "webpack --progress --color --watch",
|
|
19
|
-
"test": "tsc src/prompt.ts --outDir .tmp --module commonjs --target es2019 && node test/prompt.test.js"
|
|
20
|
-
},
|
|
21
|
-
"files": [
|
|
22
|
-
"dist",
|
|
23
|
-
"hooks",
|
|
24
|
-
"install.ps1"
|
|
25
|
-
],
|
|
26
|
-
"license": "MIT",
|
|
27
|
-
"devDependencies": {
|
|
28
|
-
"@angular/common": "^15.2.6",
|
|
29
|
-
"@angular/core": "^15.2.6",
|
|
30
|
-
"@angular/forms": "^15.2.6",
|
|
31
|
-
"@types/node": "^20.19.43",
|
|
32
|
-
"@types/webpack-env": "^1.18.4",
|
|
33
|
-
"css-loader": "^6.10.0",
|
|
34
|
-
"rxjs": "^7.5.7",
|
|
35
|
-
"sass": "^1.71.1",
|
|
36
|
-
"sass-loader": "^14.1.1",
|
|
37
|
-
"style-loader": "^3.3.4",
|
|
38
|
-
"tabby-core": "^1.0.231-nightly.0",
|
|
39
|
-
"tabby-local": "^1.0.231-nightly.0",
|
|
40
|
-
"tabby-settings": "^1.0.231-nightly.0",
|
|
41
|
-
"tabby-terminal": "^1.0.231-nightly.0",
|
|
42
|
-
"ts-loader": "^9.5.1",
|
|
43
|
-
"typescript": "^4.9.5",
|
|
44
|
-
"webpack": "^5.90.0",
|
|
45
|
-
"webpack-cli": "^5.1.4"
|
|
46
|
-
},
|
|
47
|
-
"author": {
|
|
48
|
-
"name": "junggon"
|
|
49
|
-
}
|
|
50
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "tabby-agentdeck",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Run AI coding agents side by side in Tabby - a live deck of agent sessions with status, agent-friendly hotkeys and image paste",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"tabby-plugin",
|
|
7
|
+
"tabby",
|
|
8
|
+
"ai",
|
|
9
|
+
"agent",
|
|
10
|
+
"claude-code",
|
|
11
|
+
"codex",
|
|
12
|
+
"terminal"
|
|
13
|
+
],
|
|
14
|
+
"main": "dist/index.js",
|
|
15
|
+
"typings": "dist/index.d.ts",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "webpack --progress --color",
|
|
18
|
+
"watch": "webpack --progress --color --watch",
|
|
19
|
+
"test": "tsc src/prompt.ts src/detect.ts src/claudeHooks.ts src/screen.ts --outDir .tmp --module commonjs --target es2019 --skipLibCheck --experimentalDecorators --esModuleInterop && node test/prompt.test.js && node test/detect.test.js && node test/claudeHooks.test.js && node test/screen.test.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"hooks",
|
|
24
|
+
"install.ps1"
|
|
25
|
+
],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@angular/common": "^15.2.6",
|
|
29
|
+
"@angular/core": "^15.2.6",
|
|
30
|
+
"@angular/forms": "^15.2.6",
|
|
31
|
+
"@types/node": "^20.19.43",
|
|
32
|
+
"@types/webpack-env": "^1.18.4",
|
|
33
|
+
"css-loader": "^6.10.0",
|
|
34
|
+
"rxjs": "^7.5.7",
|
|
35
|
+
"sass": "^1.71.1",
|
|
36
|
+
"sass-loader": "^14.1.1",
|
|
37
|
+
"style-loader": "^3.3.4",
|
|
38
|
+
"tabby-core": "^1.0.231-nightly.0",
|
|
39
|
+
"tabby-local": "^1.0.231-nightly.0",
|
|
40
|
+
"tabby-settings": "^1.0.231-nightly.0",
|
|
41
|
+
"tabby-terminal": "^1.0.231-nightly.0",
|
|
42
|
+
"ts-loader": "^9.5.1",
|
|
43
|
+
"typescript": "^4.9.5",
|
|
44
|
+
"webpack": "^5.90.0",
|
|
45
|
+
"webpack-cli": "^5.1.4"
|
|
46
|
+
},
|
|
47
|
+
"author": {
|
|
48
|
+
"name": "junggon"
|
|
49
|
+
}
|
|
50
|
+
}
|