eniac-slack 0.0.2
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/SPEC.md +240 -0
- package/dist/app.d.ts +8 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +44 -0
- package/dist/app.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +39 -0
- package/dist/cli.js.map +1 -0
- package/dist/handlers/mention.d.ts +10 -0
- package/dist/handlers/mention.d.ts.map +1 -0
- package/dist/handlers/mention.js +96 -0
- package/dist/handlers/mention.js.map +1 -0
- package/dist/handlers/thread.d.ts +8 -0
- package/dist/handlers/thread.d.ts.map +1 -0
- package/dist/handlers/thread.js +50 -0
- package/dist/handlers/thread.js.map +1 -0
- package/dist/services/claude.d.ts +27 -0
- package/dist/services/claude.d.ts.map +1 -0
- package/dist/services/claude.js +192 -0
- package/dist/services/claude.js.map +1 -0
- package/dist/services/git.d.ts +15 -0
- package/dist/services/git.d.ts.map +1 -0
- package/dist/services/git.js +81 -0
- package/dist/services/git.js.map +1 -0
- package/dist/services/permissions.d.ts +12 -0
- package/dist/services/permissions.d.ts.map +1 -0
- package/dist/services/permissions.js +98 -0
- package/dist/services/permissions.js.map +1 -0
- package/dist/services/slack-messenger.d.ts +11 -0
- package/dist/services/slack-messenger.d.ts.map +1 -0
- package/dist/services/slack-messenger.js +73 -0
- package/dist/services/slack-messenger.js.map +1 -0
- package/dist/utils/parse.d.ts +21 -0
- package/dist/utils/parse.d.ts.map +1 -0
- package/dist/utils/parse.js +51 -0
- package/dist/utils/parse.js.map +1 -0
- package/package.json +22 -0
- package/src/app.ts +54 -0
- package/src/cli.ts +47 -0
- package/src/handlers/mention.ts +119 -0
- package/src/handlers/thread.ts +61 -0
- package/src/services/claude.ts +280 -0
- package/src/services/git.ts +98 -0
- package/src/services/permissions.ts +131 -0
- package/src/services/slack-messenger.ts +102 -0
- package/src/utils/parse.ts +66 -0
- package/tsconfig.json +8 -0
package/SPEC.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# eniac-slack — Slack Bot for Claude Code Sessions
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Slack 소켓모드 봇으로, 멘션을 통해 Claude Code 세션을 시작하고 스레드 단위로 대화를 이어갈 수 있다. GitHub 레포지토리 연동 및 도구 실행 권한 승인을 Slack 버튼으로 처리한다.
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌─────────────┐ Socket Mode ┌──────────────────┐
|
|
11
|
+
│ Slack API │◄──────────────────►│ Bolt App │
|
|
12
|
+
│ │ │ (app.ts) │
|
|
13
|
+
└─────────────┘ └────────┬─────────┘
|
|
14
|
+
│
|
|
15
|
+
┌──────────────────┼──────────────────┐
|
|
16
|
+
│ │ │
|
|
17
|
+
┌─────▼─────┐ ┌──────▼──────┐ ┌─────▼──────┐
|
|
18
|
+
│ Mention │ │ Thread │ │ Action │
|
|
19
|
+
│ Handler │ │ Handler │ │ Handler │
|
|
20
|
+
└─────┬─────┘ └──────┬──────┘ │ (buttons) │
|
|
21
|
+
│ │ └─────┬──────┘
|
|
22
|
+
└────────┬─────────┘ │
|
|
23
|
+
│ │
|
|
24
|
+
┌──────▼──────┐ ┌──────▼──────┐
|
|
25
|
+
│ Claude │◄───────────►│ Permissions │
|
|
26
|
+
│ Service │ canUseTool │ Service │
|
|
27
|
+
│ (SDK API) │ callback │ │
|
|
28
|
+
└──────┬──────┘ └─────────────┘
|
|
29
|
+
│
|
|
30
|
+
┌──────▼──────┐
|
|
31
|
+
│ Slack │
|
|
32
|
+
│ Messenger │
|
|
33
|
+
│ (streaming) │
|
|
34
|
+
└─────────────┘
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Modules
|
|
38
|
+
|
|
39
|
+
### `cli.ts` — Entry Point
|
|
40
|
+
|
|
41
|
+
- `.env` 로드 (실행 디렉토리 및 상위 디렉토리)
|
|
42
|
+
- 필수 환경변수 검증: `SLACK_BOT_TOKEN`, `SLACK_APP_TOKEN`
|
|
43
|
+
- `REPOS_BASE_DIR` (선택, 기본: `~/.eniac/repos`)
|
|
44
|
+
- Bolt 앱 시작
|
|
45
|
+
|
|
46
|
+
### `app.ts` — Slack Bolt App
|
|
47
|
+
|
|
48
|
+
- Socket Mode로 Slack 연결
|
|
49
|
+
- 이벤트 등록:
|
|
50
|
+
- `app_mention` → `handleMention`
|
|
51
|
+
- `message` → `handleThreadMessage`
|
|
52
|
+
- 액션 등록:
|
|
53
|
+
- `approve_permission` → 도구 실행 승인
|
|
54
|
+
- `deny_permission` → 도구 실행 거부
|
|
55
|
+
|
|
56
|
+
### `handlers/mention.ts` — @Mention Handler
|
|
57
|
+
|
|
58
|
+
**새 메시지 (thread 아닌 경우):**
|
|
59
|
+
|
|
60
|
+
1. 메시지에서 봇 멘션 제거
|
|
61
|
+
2. GitHub repo 패턴 감지:
|
|
62
|
+
- **있으면:** bare clone → worktree 생성 → Claude 세션 시작
|
|
63
|
+
- **없으면:** temp 디렉토리 생성 → Claude 세션 시작
|
|
64
|
+
3. Claude 응답을 스레드에 스트리밍
|
|
65
|
+
|
|
66
|
+
**스레드 내 멘션:**
|
|
67
|
+
|
|
68
|
+
- 기존 세션이 있으면 이어서 진행
|
|
69
|
+
- 없으면 temp 디렉토리에 새 세션 생성
|
|
70
|
+
|
|
71
|
+
### `handlers/thread.ts` — Thread Reply Handler
|
|
72
|
+
|
|
73
|
+
- 봇이 활성 세션을 가진 스레드의 일반 메시지 처리
|
|
74
|
+
- 봇 자신의 메시지, subtype 메시지 무시
|
|
75
|
+
- `file_share` subtype은 허용 (이미지 첨부 메시지)
|
|
76
|
+
- 기존 세션에 이어서 Claude에 전달
|
|
77
|
+
|
|
78
|
+
### `services/claude.ts` — Claude Code Session Manager
|
|
79
|
+
|
|
80
|
+
**세션 관리:**
|
|
81
|
+
|
|
82
|
+
- `Map<thread_ts, Session>` 으로 메모리에 유지
|
|
83
|
+
- `~/.eniac/sessions.json`에 영구 저장 (서버 재시작 시 복원)
|
|
84
|
+
- 세션 ID: UUID (랜덤 생성)
|
|
85
|
+
- `hasStarted` 플래그로 첫 실행(`sessionId`) / 재개(`resume`) 구분
|
|
86
|
+
|
|
87
|
+
**세션 생명주기:**
|
|
88
|
+
|
|
89
|
+
- `createSession(threadTs, workDir, authorUserId)` → 새 세션 생성
|
|
90
|
+
- `getSession(threadTs)` → 기존 세션 조회
|
|
91
|
+
- `lastActivityAt` 매 대화마다 갱신
|
|
92
|
+
- 서버 시작 시 2주 이상 비활성 세션 자동 정리 (SDK `.jsonl` + worktree 삭제)
|
|
93
|
+
|
|
94
|
+
**Claude SDK 호출:**
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
query({
|
|
98
|
+
prompt: userMessage,
|
|
99
|
+
options: {
|
|
100
|
+
cwd: session.workDir,
|
|
101
|
+
canUseTool, // 커스텀 권한 콜백
|
|
102
|
+
permissionMode: "bypassPermissions", // SDK 내장 권한 비활성화
|
|
103
|
+
allowDangerouslySkipPermissions: true,
|
|
104
|
+
includePartialMessages: true, // 실시간 스트리밍
|
|
105
|
+
sessionId: session.sessionId, // 첫 호출
|
|
106
|
+
// 또는
|
|
107
|
+
resume: session.sessionId, // 이후 호출
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**도구 권한 분류:**
|
|
113
|
+
|
|
114
|
+
| 자동 허용 | Slack 버튼 승인 필요 |
|
|
115
|
+
|-----------|---------------------|
|
|
116
|
+
| Read, Glob, Grep | Bash |
|
|
117
|
+
| WebSearch, WebFetch | Edit, Write |
|
|
118
|
+
| Agent, TodoRead | 기타 모든 도구 |
|
|
119
|
+
| ListMcpResources, ReadMcpResource | |
|
|
120
|
+
|
|
121
|
+
**ChatEvent 타입:**
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
type ChatEvent =
|
|
125
|
+
| { type: "text"; content: string }
|
|
126
|
+
| { type: "error"; message: string };
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `services/permissions.ts` — Permission Bridge
|
|
130
|
+
|
|
131
|
+
**플로우:**
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
Claude canUseTool 콜백 호출 (위험 도구)
|
|
135
|
+
→ Slack에 Block Kit 버튼 메시지 포스트
|
|
136
|
+
┌──────────────────────────────────┐
|
|
137
|
+
│ 🔒 Permission Required │
|
|
138
|
+
│ │
|
|
139
|
+
│ Tool: `Bash` │
|
|
140
|
+
│ ``` │
|
|
141
|
+
│ npm install express │
|
|
142
|
+
│ ``` │
|
|
143
|
+
│ │
|
|
144
|
+
│ [Approve] [Deny] │
|
|
145
|
+
└──────────────────────────────────┘
|
|
146
|
+
→ Promise로 대기 (5분 타임아웃)
|
|
147
|
+
→ 스레드 작성자가 버튼 클릭
|
|
148
|
+
→ Promise resolve(true/false)
|
|
149
|
+
→ 버튼 메시지를 결과로 업데이트
|
|
150
|
+
"✅ Approved by @user" 또는 "🚫 Denied by @user"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**보안:**
|
|
154
|
+
|
|
155
|
+
- 스레드 작성자만 승인/거부 가능 (`authorUserId` 검증)
|
|
156
|
+
- 5분 타임아웃 후 자동 거부
|
|
157
|
+
|
|
158
|
+
**API:**
|
|
159
|
+
|
|
160
|
+
- `requestPermission(client, channel, threadTs, permissionId, toolName, description, authorUserId)` → `Promise<boolean>`
|
|
161
|
+
- `resolvePermission(client, permissionId, granted, clickedUserId)` → `Promise<void>`
|
|
162
|
+
|
|
163
|
+
### `services/slack-messenger.ts` — Streaming Message Updater
|
|
164
|
+
|
|
165
|
+
- 초기 메시지 "⏳ Thinking..." 포스트
|
|
166
|
+
- `ChatEvent` 스트림을 소비하며:
|
|
167
|
+
- `text` → 텍스트 누적 후 메시지 업데이트
|
|
168
|
+
- `error` → 에러 메시지 추가
|
|
169
|
+
- 500ms 스로틀링으로 Slack rate limit 방지
|
|
170
|
+
- 중간 업데이트 시 단어 경계에서 텍스트 자르기 (한글 punycode 버그 방지)
|
|
171
|
+
|
|
172
|
+
### `services/git.ts` — Repository Manager
|
|
173
|
+
|
|
174
|
+
**전략: bare repo + worktree**
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
{REPOS_BASE_DIR}/
|
|
178
|
+
└── {owner}/
|
|
179
|
+
├── {repo}.git/ # bare clone (공유)
|
|
180
|
+
└── {repo}/
|
|
181
|
+
└── worktrees/
|
|
182
|
+
├── 1710000000/ # worktree 1 (branch: eniac/1710000000)
|
|
183
|
+
└── 1710000001/ # worktree 2 (branch: eniac/1710000001)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
1. bare repo가 없으면 `git clone --bare`
|
|
187
|
+
2. bare repo가 있으면 `git fetch --all`
|
|
188
|
+
3. `git worktree add -b eniac/{timestamp} {path} {defaultBranch}` 로 작업 디렉토리 생성
|
|
189
|
+
4. default branch는 `HEAD` symbolic ref에서 자동 감지
|
|
190
|
+
5. 스레드마다 독립 브랜치 → PR 생성 가능
|
|
191
|
+
|
|
192
|
+
### `utils/parse.ts` — Message Parser
|
|
193
|
+
|
|
194
|
+
- `removeMention(text)`: `<@BOTID>` 제거
|
|
195
|
+
- `extractGithubRepo(text)`: GitHub repo 추출
|
|
196
|
+
- `https://github.com/owner/repo` ✅
|
|
197
|
+
- `github.com/owner/repo` ✅
|
|
198
|
+
- `owner/repo` ✅ (유효한 GitHub 네이밍 규칙 충족 시)
|
|
199
|
+
- `../path` ❌ (파일 경로 형태 거부)
|
|
200
|
+
|
|
201
|
+
## Data Storage
|
|
202
|
+
|
|
203
|
+
| 데이터 | 경로 | 내용 |
|
|
204
|
+
|--------|------|------|
|
|
205
|
+
| 세션 매핑 | `~/.eniac/sessions.json` | `threadTs → { sessionId, workDir, authorUserId, ... }` |
|
|
206
|
+
| SDK 대화 기록 | `~/.claude/projects/{cwd-encoded}/{sessionId}.jsonl` | 전체 대화 히스토리 (SDK 자동 관리) |
|
|
207
|
+
| Git bare repos | `{REPOS_BASE_DIR}/{owner}/{repo}.git/` | 공유 bare clone |
|
|
208
|
+
| Git worktrees | `{REPOS_BASE_DIR}/{owner}/{repo}/worktrees/{timestamp}/` | 스레드별 작업 디렉토리 |
|
|
209
|
+
|
|
210
|
+
**세션 만료:** `lastActivityAt` 기준 2주 경과 시 서버 시작 시점에 자동 정리 (세션 파일 + worktree 삭제)
|
|
211
|
+
|
|
212
|
+
## Environment Variables
|
|
213
|
+
|
|
214
|
+
| 변수 | 필수 | 설명 |
|
|
215
|
+
|------|------|------|
|
|
216
|
+
| `SLACK_BOT_TOKEN` | ✅ | `xoxb-` 봇 토큰 |
|
|
217
|
+
| `SLACK_APP_TOKEN` | ✅ | `xapp-` 앱 레벨 토큰 (소켓모드) |
|
|
218
|
+
| `REPOS_BASE_DIR` | ❌ | 레포 저장 경로 (기본: `~/.eniac/repos`) |
|
|
219
|
+
|
|
220
|
+
## Tech Stack
|
|
221
|
+
|
|
222
|
+
- **Runtime:** Node.js ≥ 18
|
|
223
|
+
- **Language:** TypeScript (ES2022, ESM)
|
|
224
|
+
- **Package Manager:** Yarn 4 (Berry, node-modules linker)
|
|
225
|
+
- **Monorepo:** Yarn Workspaces (`services/*`, `packages/*`)
|
|
226
|
+
- **Slack:** `@slack/bolt` v4 (Socket Mode)
|
|
227
|
+
- **Claude:** `@anthropic-ai/claude-agent-sdk` v0.2.72 (`query()` API)
|
|
228
|
+
- **Git:** `simple-git`
|
|
229
|
+
|
|
230
|
+
## Slack App 설정 요구사항
|
|
231
|
+
|
|
232
|
+
1. **Socket Mode** 활성화
|
|
233
|
+
2. **Event Subscriptions:**
|
|
234
|
+
- `app_mention`
|
|
235
|
+
- `message.channels` / `message.groups`
|
|
236
|
+
3. **Bot Token Scopes:**
|
|
237
|
+
- `app_mentions:read`
|
|
238
|
+
- `chat:write`
|
|
239
|
+
- `channels:history` / `groups:history`
|
|
240
|
+
4. **Interactivity** 활성화 (버튼 클릭 처리용)
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAKlC,MAAM,WAAW,SAAS;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,CA0CvE"}
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { App } from "@slack/bolt";
|
|
2
|
+
import { handleMention, setReposBaseDir } from "./handlers/mention.js";
|
|
3
|
+
import { handleThreadMessage } from "./handlers/thread.js";
|
|
4
|
+
import { resolvePermission } from "./services/permissions.js";
|
|
5
|
+
export async function createAndStartApp(config) {
|
|
6
|
+
const { slackBotToken, slackAppToken, reposBaseDir } = config;
|
|
7
|
+
setReposBaseDir(reposBaseDir);
|
|
8
|
+
const app = new App({
|
|
9
|
+
token: slackBotToken,
|
|
10
|
+
appToken: slackAppToken,
|
|
11
|
+
socketMode: true,
|
|
12
|
+
});
|
|
13
|
+
// Event handlers
|
|
14
|
+
app.event("app_mention", handleMention);
|
|
15
|
+
app.event("message", handleThreadMessage);
|
|
16
|
+
// Permission button handlers
|
|
17
|
+
app.action("approve_permission", async ({ action, ack, client, body }) => {
|
|
18
|
+
await ack();
|
|
19
|
+
console.log(`[action] approve_permission fired! action.type=${action.type}, body.user=${JSON.stringify(body.user)}`);
|
|
20
|
+
if (action.type !== "button")
|
|
21
|
+
return;
|
|
22
|
+
const permissionId = action.value;
|
|
23
|
+
const clickedUserId = body.user?.id ?? "someone";
|
|
24
|
+
console.log(`[action] approve: permissionId=${permissionId}, clickedUserId=${clickedUserId}`);
|
|
25
|
+
if (permissionId) {
|
|
26
|
+
await resolvePermission(client, permissionId, true, clickedUserId);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
app.action("deny_permission", async ({ action, ack, client, body }) => {
|
|
30
|
+
await ack();
|
|
31
|
+
console.log(`[action] deny_permission fired!`);
|
|
32
|
+
if (action.type !== "button")
|
|
33
|
+
return;
|
|
34
|
+
const permissionId = action.value;
|
|
35
|
+
const clickedUserId = body.user?.id ?? "someone";
|
|
36
|
+
console.log(`[action] deny: permissionId=${permissionId}, clickedUserId=${clickedUserId}`);
|
|
37
|
+
if (permissionId) {
|
|
38
|
+
await resolvePermission(client, permissionId, false, clickedUserId);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
await app.start();
|
|
42
|
+
return app;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=app.js.map
|
package/dist/app.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAQ9D,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAiB;IACvD,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC;IAE9D,eAAe,CAAC,YAAY,CAAC,CAAC;IAE9B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC;QAClB,KAAK,EAAE,aAAa;QACpB,QAAQ,EAAE,aAAa;QACvB,UAAU,EAAE,IAAI;KACjB,CAAC,CAAC;IAEH,iBAAiB;IACjB,GAAG,CAAC,KAAK,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IACxC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;IAE1C,6BAA6B;IAC7B,GAAG,CAAC,MAAM,CAAC,oBAAoB,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE;QACvE,MAAM,GAAG,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC,kDAAkD,MAAM,CAAC,IAAI,eAAe,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrH,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO;QACrC,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC;QAClC,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,SAAS,CAAC;QACjD,OAAO,CAAC,GAAG,CAAC,kCAAkC,YAAY,mBAAmB,aAAa,EAAE,CAAC,CAAC;QAC9F,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;QACrE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,MAAM,CAAC,iBAAiB,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE;QACpE,MAAM,GAAG,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;QAC/C,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO;QACrC,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC;QAClC,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,SAAS,CAAC;QACjD,OAAO,CAAC,GAAG,CAAC,+BAA+B,YAAY,mBAAmB,aAAa,EAAE,CAAC,CAAC;QAC3F,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC;QACtE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;IAClB,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import dotenv from "dotenv";
|
|
5
|
+
import { createAndStartApp } from "./app.js";
|
|
6
|
+
// Load .env — try cwd first, then walk up to find it
|
|
7
|
+
dotenv.config();
|
|
8
|
+
dotenv.config({ path: path.resolve(process.cwd(), "../../.env") });
|
|
9
|
+
function requireEnv(name) {
|
|
10
|
+
const value = process.env[name];
|
|
11
|
+
if (!value) {
|
|
12
|
+
console.error(`[eniac-slack] Missing required environment variable: ${name}`);
|
|
13
|
+
console.error(`Create a .env file or export ${name} in your shell.`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
async function main() {
|
|
19
|
+
const slackBotToken = requireEnv("SLACK_BOT_TOKEN");
|
|
20
|
+
const slackAppToken = requireEnv("SLACK_APP_TOKEN");
|
|
21
|
+
const reposBaseDir = process.env["REPOS_BASE_DIR"]?.replace(/^~/, os.homedir()) ??
|
|
22
|
+
path.join(os.homedir(), ".eniac", "repos");
|
|
23
|
+
console.log("[eniac-slack] Starting Slack bot...");
|
|
24
|
+
console.log(`[eniac-slack] Repos base directory: ${reposBaseDir}`);
|
|
25
|
+
try {
|
|
26
|
+
await createAndStartApp({
|
|
27
|
+
slackBotToken,
|
|
28
|
+
slackAppToken,
|
|
29
|
+
reposBaseDir,
|
|
30
|
+
});
|
|
31
|
+
console.log("[eniac-slack] Bot is running! Listening for events...");
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.error("[eniac-slack] Failed to start:", error);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
main();
|
|
39
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C,qDAAqD;AACrD,MAAM,CAAC,MAAM,EAAE,CAAC;AAChB,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC;AAEnE,SAAS,UAAU,CAAC,IAAY;IAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,wDAAwD,IAAI,EAAE,CAAC,CAAC;QAC9E,OAAO,CAAC,KAAK,CAAC,gCAAgC,IAAI,iBAAiB,CAAC,CAAC;QACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,aAAa,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IACpD,MAAM,aAAa,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAEpD,MAAM,YAAY,GAChB,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC;QAC1D,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAE7C,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC,uCAAuC,YAAY,EAAE,CAAC,CAAC;IAEnE,IAAI,CAAC;QACH,MAAM,iBAAiB,CAAC;YACtB,aAAa;YACb,aAAa;YACb,YAAY;SACb,CAAC,CAAC;QAEH,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;IACvE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,KAAK,CAAC,CAAC;QACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AllMiddlewareArgs, SlackEventMiddlewareArgs } from "@slack/bolt";
|
|
2
|
+
export declare function setReposBaseDir(dir: string): void;
|
|
3
|
+
/**
|
|
4
|
+
* Handle `app_mention` events.
|
|
5
|
+
*
|
|
6
|
+
* - New mention (not in a thread): start a new Claude session.
|
|
7
|
+
* - Mention in a thread: forward to thread handler logic.
|
|
8
|
+
*/
|
|
9
|
+
export declare function handleMention(args: AllMiddlewareArgs & SlackEventMiddlewareArgs<"app_mention">): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=mention.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mention.d.ts","sourceRoot":"","sources":["../../src/handlers/mention.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAW/E,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEjD;AASD;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,iBAAiB,GAAG,wBAAwB,CAAC,aAAa,CAAC,GAChE,OAAO,CAAC,IAAI,CAAC,CAgEf"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { removeMention, extractGithubRepo } from "../utils/parse.js";
|
|
2
|
+
import { createSession, getSession, chat } from "../services/claude.js";
|
|
3
|
+
import { prepareWorkDir } from "../services/git.js";
|
|
4
|
+
import { postStreamingReply } from "../services/slack-messenger.js";
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
let reposBaseDir;
|
|
9
|
+
export function setReposBaseDir(dir) {
|
|
10
|
+
reposBaseDir = dir;
|
|
11
|
+
}
|
|
12
|
+
function getReposBaseDir() {
|
|
13
|
+
if (!reposBaseDir) {
|
|
14
|
+
reposBaseDir = path.join(os.homedir(), ".eniac", "repos");
|
|
15
|
+
}
|
|
16
|
+
return reposBaseDir;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Handle `app_mention` events.
|
|
20
|
+
*
|
|
21
|
+
* - New mention (not in a thread): start a new Claude session.
|
|
22
|
+
* - Mention in a thread: forward to thread handler logic.
|
|
23
|
+
*/
|
|
24
|
+
export async function handleMention(args) {
|
|
25
|
+
const { event, client } = args;
|
|
26
|
+
const { text, channel, ts, thread_ts } = event;
|
|
27
|
+
const authorUserId = event.user ?? "unknown";
|
|
28
|
+
// If the mention is inside an existing thread, treat it as a thread reply
|
|
29
|
+
if (thread_ts) {
|
|
30
|
+
await handleThreadedMention(client, channel, thread_ts, text, authorUserId);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// New conversation — ts becomes the thread_ts
|
|
34
|
+
const threadTs = ts;
|
|
35
|
+
const cleanText = removeMention(text);
|
|
36
|
+
if (!cleanText) {
|
|
37
|
+
await client.chat.postMessage({
|
|
38
|
+
channel,
|
|
39
|
+
thread_ts: threadTs,
|
|
40
|
+
text: "How can I help you? You can ask me coding questions, or mention a GitHub repo (e.g. `owner/repo`) to work with its code.",
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Check for GitHub repo in message
|
|
45
|
+
const repoInfo = extractGithubRepo(cleanText);
|
|
46
|
+
let workDir;
|
|
47
|
+
if (repoInfo) {
|
|
48
|
+
try {
|
|
49
|
+
await client.chat.postMessage({
|
|
50
|
+
channel,
|
|
51
|
+
thread_ts: threadTs,
|
|
52
|
+
text: `:file_folder: Setting up repository \`${repoInfo.owner}/${repoInfo.repo}\`...`,
|
|
53
|
+
});
|
|
54
|
+
workDir = await prepareWorkDir(repoInfo, getReposBaseDir());
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
58
|
+
await client.chat.postMessage({
|
|
59
|
+
channel,
|
|
60
|
+
thread_ts: threadTs,
|
|
61
|
+
text: `:x: Failed to set up repository: ${message}`,
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// Create a temp directory for non-repo conversations
|
|
68
|
+
workDir = await fs.mkdtemp(path.join(os.tmpdir(), "eniac-"));
|
|
69
|
+
}
|
|
70
|
+
// Create a Claude session
|
|
71
|
+
createSession(threadTs, workDir, authorUserId);
|
|
72
|
+
// Build context-aware first message
|
|
73
|
+
let userMessage = cleanText;
|
|
74
|
+
if (repoInfo) {
|
|
75
|
+
userMessage = `[Working directory: ${workDir} — Repository: ${repoInfo.owner}/${repoInfo.repo}]\n\n${cleanText}`;
|
|
76
|
+
}
|
|
77
|
+
// Stream the response
|
|
78
|
+
const stream = chat(threadTs, userMessage, client, channel);
|
|
79
|
+
await postStreamingReply(client, channel, threadTs, stream);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Handle a mention that occurs inside an existing thread.
|
|
83
|
+
*/
|
|
84
|
+
async function handleThreadedMention(client, channel, threadTs, text, authorUserId) {
|
|
85
|
+
const cleanText = removeMention(text);
|
|
86
|
+
if (!cleanText)
|
|
87
|
+
return;
|
|
88
|
+
let session = getSession(threadTs);
|
|
89
|
+
if (!session) {
|
|
90
|
+
const workDir = await fs.mkdtemp(path.join(os.tmpdir(), "eniac-"));
|
|
91
|
+
session = createSession(threadTs, workDir, authorUserId);
|
|
92
|
+
}
|
|
93
|
+
const stream = chat(threadTs, cleanText, client, channel);
|
|
94
|
+
await postStreamingReply(client, channel, threadTs, stream);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=mention.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mention.js","sourceRoot":"","sources":["../../src/handlers/mention.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AllMiddlewareArgs, SlackEventMiddlewareArgs } from "@slack/bolt";
|
|
2
|
+
/**
|
|
3
|
+
* Handle messages posted in threads where the bot has an active session.
|
|
4
|
+
*
|
|
5
|
+
* Only responds to human messages (ignores bot messages and subtypes).
|
|
6
|
+
*/
|
|
7
|
+
export declare function handleThreadMessage(args: AllMiddlewareArgs & SlackEventMiddlewareArgs<"message">): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=thread.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thread.d.ts","sourceRoot":"","sources":["../../src/handlers/thread.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAI/E;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,iBAAiB,GAAG,wBAAwB,CAAC,SAAS,CAAC,GAC5D,OAAO,CAAC,IAAI,CAAC,CAiDf"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { getSession, chat } from "../services/claude.js";
|
|
2
|
+
import { postStreamingReply } from "../services/slack-messenger.js";
|
|
3
|
+
/**
|
|
4
|
+
* Handle messages posted in threads where the bot has an active session.
|
|
5
|
+
*
|
|
6
|
+
* Only responds to human messages (ignores bot messages and subtypes).
|
|
7
|
+
*/
|
|
8
|
+
export async function handleThreadMessage(args) {
|
|
9
|
+
const { event, client, context } = args;
|
|
10
|
+
const subtype = event.subtype ?? "none";
|
|
11
|
+
const threadTs = "thread_ts" in event ? event.thread_ts : undefined;
|
|
12
|
+
const botId = "bot_id" in event ? event.bot_id : undefined;
|
|
13
|
+
const userId = "user" in event ? event.user : undefined;
|
|
14
|
+
console.log(`[thread] message event: subtype=${subtype}, thread_ts=${threadTs ?? "none"}, bot_id=${botId ?? "none"}, user=${userId ?? "none"}`);
|
|
15
|
+
// Skip non-user subtypes (message_changed, bot_message, etc.)
|
|
16
|
+
// But allow file_share (image/file attachments) — those are real user messages
|
|
17
|
+
const allowedSubtypes = new Set(["file_share"]);
|
|
18
|
+
if (event.subtype !== undefined && !allowedSubtypes.has(event.subtype)) {
|
|
19
|
+
console.log(`[thread] skipped: subtype=${subtype}`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// Must be in a thread
|
|
23
|
+
if (!threadTs) {
|
|
24
|
+
console.log(`[thread] skipped: not in a thread`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Ignore messages from the bot itself
|
|
28
|
+
if (botId || userId === context.botUserId) {
|
|
29
|
+
console.log(`[thread] skipped: bot message`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const text = "text" in event ? event.text : undefined;
|
|
33
|
+
const channel = event.channel;
|
|
34
|
+
// Only respond if we have an active session for this thread
|
|
35
|
+
const session = getSession(threadTs);
|
|
36
|
+
if (!session) {
|
|
37
|
+
console.log(`[thread] skipped: no session for thread_ts=${threadTs}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
console.log(`[thread] session found! sessionId=${session.sessionId}, text=${text?.slice(0, 50)}`);
|
|
41
|
+
if (!text || text.trim().length === 0)
|
|
42
|
+
return;
|
|
43
|
+
// Strip any bot mentions from the text (user might @mention the bot in-thread)
|
|
44
|
+
const cleanText = text.replace(/<@[A-Z0-9]+>/g, "").trim();
|
|
45
|
+
if (!cleanText)
|
|
46
|
+
return;
|
|
47
|
+
const stream = chat(threadTs, cleanText, client, channel);
|
|
48
|
+
await postStreamingReply(client, channel, threadTs, stream);
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=thread.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thread.js","sourceRoot":"","sources":["../../src/handlers/thread.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEpE;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAA6D;IAE7D,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAExC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,MAAM,CAAC;IACxC,MAAM,QAAQ,GAAG,WAAW,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IACpE,MAAM,KAAK,GAAG,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3D,MAAM,MAAM,GAAG,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACxD,OAAO,CAAC,GAAG,CAAC,mCAAmC,OAAO,eAAe,QAAQ,IAAI,MAAM,YAAY,KAAK,IAAI,MAAM,UAAU,MAAM,IAAI,MAAM,EAAE,CAAC,CAAC;IAEhJ,8DAA8D;IAC9D,+EAA+E;IAC/E,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;IAChD,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACvE,OAAO,CAAC,GAAG,CAAC,6BAA6B,OAAO,EAAE,CAAC,CAAC;QACpD,OAAO;IACT,CAAC;IAED,sBAAsB;IACtB,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;QACjD,OAAO;IACT,CAAC;IAED,sCAAsC;IACtC,IAAI,KAAK,IAAI,MAAM,KAAK,OAAO,CAAC,SAAS,EAAE,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;QAC7C,OAAO;IACT,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACtD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAE9B,4DAA4D;IAC5D,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,8CAA8C,QAAQ,EAAE,CAAC,CAAC;QACtE,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,qCAAqC,OAAO,CAAC,SAAS,UAAU,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;IAElG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAE9C,+EAA+E;IAC/E,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3D,IAAI,CAAC,SAAS;QAAE,OAAO;IAEvB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1D,MAAM,kBAAkB,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { WebClient } from "@slack/web-api";
|
|
2
|
+
interface Session {
|
|
3
|
+
sessionId: string;
|
|
4
|
+
workDir: string;
|
|
5
|
+
hasStarted: boolean;
|
|
6
|
+
authorUserId: string;
|
|
7
|
+
createdAt: number;
|
|
8
|
+
lastActivityAt: number;
|
|
9
|
+
}
|
|
10
|
+
export type ChatEvent = {
|
|
11
|
+
type: "text";
|
|
12
|
+
content: string;
|
|
13
|
+
} | {
|
|
14
|
+
type: "error";
|
|
15
|
+
message: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function createSession(threadTs: string, workDir: string, authorUserId: string): Session;
|
|
18
|
+
export declare function getSession(threadTs: string): Session | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Send a message to a Claude session via the SDK.
|
|
21
|
+
*
|
|
22
|
+
* Uses `canUseTool` callback to handle permission requests
|
|
23
|
+
* through Slack interactive buttons.
|
|
24
|
+
*/
|
|
25
|
+
export declare function chat(threadTs: string, userMessage: string, slackClient: WebClient, channel: string): AsyncGenerator<ChatEvent, void, unknown>;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=claude.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/services/claude.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGhD,UAAU,OAAO;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAgFvC,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,GACnB,OAAO,CAaT;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAEhE;AAkBD;;;;;GAKG;AACH,wBAAuB,IAAI,CACzB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,SAAS,EACtB,OAAO,EAAE,MAAM,GACd,cAAc,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,CA6H1C"}
|