choavis-agent 1.4.0 → 1.4.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/.env.example +3 -2
- package/README.md +120 -205
- package/dist/agent/adapters/claude.d.ts +13 -0
- package/dist/agent/adapters/claude.d.ts.map +1 -0
- package/dist/agent/adapters/claude.js +199 -0
- package/dist/agent/adapters/claude.js.map +1 -0
- package/dist/agent/protocol.d.ts +56 -0
- package/dist/agent/protocol.d.ts.map +1 -0
- package/dist/agent/protocol.js +9 -0
- package/dist/agent/protocol.js.map +1 -0
- package/dist/agent/queue.d.ts.map +1 -1
- package/dist/agent/queue.js +4 -2
- package/dist/agent/queue.js.map +1 -1
- package/dist/agent/runner.d.ts +8 -0
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +13 -39
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/session.d.ts +27 -1
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +79 -3
- package/dist/agent/session.js.map +1 -1
- package/dist/agent/usage.d.ts +8 -5
- package/dist/agent/usage.d.ts.map +1 -1
- package/dist/agent/usage.js +21 -0
- package/dist/agent/usage.js.map +1 -1
- package/dist/approval/server.d.ts +11 -1
- package/dist/approval/server.d.ts.map +1 -1
- package/dist/approval/server.js +46 -4
- package/dist/approval/server.js.map +1 -1
- package/dist/approval/setup.d.ts +5 -0
- package/dist/approval/setup.d.ts.map +1 -1
- package/dist/approval/setup.js +31 -0
- package/dist/approval/setup.js.map +1 -1
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +18 -8
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/onboard.d.ts.map +1 -1
- package/dist/cli/onboard.js +299 -51
- package/dist/cli/onboard.js.map +1 -1
- package/dist/cli.js +10 -5
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -1
- package/dist/config.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/slack/commands/compact.js +2 -2
- package/dist/slack/commands/compact.js.map +1 -1
- package/dist/slack/commands/help.d.ts.map +1 -1
- package/dist/slack/commands/help.js +16 -11
- package/dist/slack/commands/help.js.map +1 -1
- package/dist/slack/commands/index.d.ts.map +1 -1
- package/dist/slack/commands/index.js +26 -0
- package/dist/slack/commands/index.js.map +1 -1
- package/dist/slack/commands/inject.d.ts +3 -0
- package/dist/slack/commands/inject.d.ts.map +1 -0
- package/dist/slack/commands/inject.js +44 -0
- package/dist/slack/commands/inject.js.map +1 -0
- package/dist/slack/commands/memory.js +7 -7
- package/dist/slack/commands/memory.js.map +1 -1
- package/dist/slack/commands/model.d.ts +3 -0
- package/dist/slack/commands/model.d.ts.map +1 -0
- package/dist/slack/commands/model.js +43 -0
- package/dist/slack/commands/model.js.map +1 -0
- package/dist/slack/commands/new.js +1 -1
- package/dist/slack/commands/new.js.map +1 -1
- package/dist/slack/commands/permission.d.ts +3 -0
- package/dist/slack/commands/permission.d.ts.map +1 -0
- package/dist/slack/commands/permission.js +51 -0
- package/dist/slack/commands/permission.js.map +1 -0
- package/dist/slack/commands/project.js +2 -2
- package/dist/slack/commands/project.js.map +1 -1
- package/dist/slack/commands/resume.d.ts +3 -0
- package/dist/slack/commands/resume.d.ts.map +1 -0
- package/dist/slack/commands/resume.js +93 -0
- package/dist/slack/commands/resume.js.map +1 -0
- package/dist/slack/commands/session-info.d.ts +3 -0
- package/dist/slack/commands/session-info.d.ts.map +1 -0
- package/dist/slack/commands/session-info.js +33 -0
- package/dist/slack/commands/session-info.js.map +1 -0
- package/dist/slack/commands/sessions.d.ts +3 -0
- package/dist/slack/commands/sessions.d.ts.map +1 -0
- package/dist/slack/commands/sessions.js +54 -0
- package/dist/slack/commands/sessions.js.map +1 -0
- package/dist/slack/commands/status.d.ts.map +1 -1
- package/dist/slack/commands/status.js +7 -2
- package/dist/slack/commands/status.js.map +1 -1
- package/dist/slack/commands/usage.d.ts +5 -0
- package/dist/slack/commands/usage.d.ts.map +1 -1
- package/dist/slack/commands/usage.js +93 -12
- package/dist/slack/commands/usage.js.map +1 -1
- package/dist/slack/context.d.ts +11 -8
- package/dist/slack/context.d.ts.map +1 -1
- package/dist/slack/context.js +36 -14
- package/dist/slack/context.js.map +1 -1
- package/dist/slack/file-downloader.d.ts +18 -0
- package/dist/slack/file-downloader.d.ts.map +1 -0
- package/dist/slack/file-downloader.js +196 -0
- package/dist/slack/file-downloader.js.map +1 -0
- package/dist/slack/formatter.d.ts +0 -4
- package/dist/slack/formatter.d.ts.map +1 -1
- package/dist/slack/formatter.js +0 -8
- package/dist/slack/formatter.js.map +1 -1
- package/dist/slack/handlers.d.ts +3 -4
- package/dist/slack/handlers.d.ts.map +1 -1
- package/dist/slack/handlers.js +372 -259
- package/dist/slack/handlers.js.map +1 -1
- package/dist/slack/link-resolver.d.ts +24 -0
- package/dist/slack/link-resolver.d.ts.map +1 -0
- package/dist/slack/link-resolver.js +52 -0
- package/dist/slack/link-resolver.js.map +1 -0
- package/dist/slack/middleware.d.ts +17 -0
- package/dist/slack/middleware.d.ts.map +1 -0
- package/dist/slack/middleware.js +73 -0
- package/dist/slack/middleware.js.map +1 -0
- package/dist/slack/slack-retry.d.ts +18 -0
- package/dist/slack/slack-retry.d.ts.map +1 -0
- package/dist/slack/slack-retry.js +71 -0
- package/dist/slack/slack-retry.js.map +1 -0
- package/dist/slack/tool-formatter.d.ts +18 -0
- package/dist/slack/tool-formatter.d.ts.map +1 -0
- package/dist/slack/tool-formatter.js +154 -0
- package/dist/slack/tool-formatter.js.map +1 -0
- package/dist/slack/types.d.ts +22 -0
- package/dist/slack/types.d.ts.map +1 -0
- package/dist/slack/types.js +2 -0
- package/dist/slack/types.js.map +1 -0
- package/package.json +10 -10
- package/scripts/pretool-hook.mjs +0 -0
- package/slack-app-manifest.json +12 -1
package/.env.example
CHANGED
|
@@ -23,8 +23,9 @@ SLACK_APP_TOKEN=xapp-your-app-token
|
|
|
23
23
|
# 프로젝트 레지스트리 (JSON) - Slack에서 "프로젝트: 이름"으로 전환 가능
|
|
24
24
|
# AGENT_PROJECTS={"my-project":"/path/to/project"}
|
|
25
25
|
|
|
26
|
-
# 허용된 Slack 사용자 ID (쉼표
|
|
27
|
-
#
|
|
26
|
+
# 허용된 Slack 사용자 ID (쉼표 구분) — 반드시 설정해야 함
|
|
27
|
+
# 미설정 시 아무도 사용할 수 없음 (전체 거부)
|
|
28
|
+
ALLOWED_SLACK_USERS=U12345678,U87654321
|
|
28
29
|
|
|
29
30
|
# 동시 Claude 프로세스 제한 (기본값: 5)
|
|
30
31
|
# MAX_CONCURRENT_AGENTS=5
|
package/README.md
CHANGED
|
@@ -1,271 +1,186 @@
|
|
|
1
1
|
# Choavis Agent
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Slack에서 Claude Code를 제어하는 로컬 에이전트.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/choavis-agent)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://nodejs.org/)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
Choavis Agent는 로컬 [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)를 Slack과 연결합니다. 터미널 앞에 앉아 있지 않아도 Slack 메시지 하나로 로컬 머신에서 개발 작업을 수행할 수 있습니다.
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
<!-- 데모 GIF가 있다면 여기에 추가 -->
|
|
13
|
+
<!--  -->
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
## 왜 Choavis Agent인가?
|
|
15
|
-
|
|
16
|
-
### 문제
|
|
17
|
-
- Claude Code는 강력하지만, **터미널 앞에 앉아있어야** 사용할 수 있다
|
|
18
|
-
- 클라우드 기반 코딩 에이전트는 편리하지만, **내 코드가 외부 서버를 거친다**
|
|
19
|
-
- 팀원들과 AI 에이전트를 공유하려면 별도 인프라가 필요하다
|
|
20
|
-
|
|
21
|
-
### 해결
|
|
22
|
-
- Slack이 리모컨, 내 PC가 실행 머신. **코드는 내 로컬에서만 돌아간다**
|
|
23
|
-
- `npm install` 한 번으로 설치, 대화형 셋업으로 3분 만에 시작
|
|
24
|
-
- Slack만 있으면 스마트폰, 태블릿, 어디서든 접근 가능
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## 이런 것들이 가능합니다
|
|
29
|
-
|
|
30
|
-
### 코드 작성 & 수정
|
|
31
|
-
> "UserService에 이메일 중복 체크 로직 추가해줘"
|
|
32
|
-
|
|
33
|
-
슬랙 메시지 하나로 코드 수정, 파일 생성, 리팩토링이 실행됩니다.
|
|
34
|
-
|
|
35
|
-
### 버그 분석 & 수정
|
|
36
|
-
> "로그인 API에서 500 에러 나는데, AuthController 확인해봐"
|
|
37
|
-
|
|
38
|
-
에이전트가 코드를 읽고, 원인을 분석하고, 수정안을 제시하거나 직접 고칩니다.
|
|
39
|
-
|
|
40
|
-
### Git & 배포
|
|
41
|
-
> "지금까지 변경사항 커밋하고 PR 만들어줘"
|
|
42
|
-
|
|
43
|
-
Git add, commit, push, PR 생성까지 한 번에 처리합니다.
|
|
44
|
-
|
|
45
|
-
### 코드 리뷰 & 분석
|
|
46
|
-
> "src/services/ 디렉토리 구조 분석해줘"
|
|
47
|
-
|
|
48
|
-
프로젝트 구조를 파악하고, 코드 품질을 분석하고, 개선안을 제안합니다.
|
|
49
|
-
|
|
50
|
-
### 업무 자동화
|
|
51
|
-
> "테스트 돌려서 결과 알려줘"
|
|
52
|
-
|
|
53
|
-
반복 작업을 에이전트에게 위임하여 개발 워크플로우를 자동화합니다.
|
|
54
|
-
|
|
55
|
-
### 멀티 프로젝트
|
|
56
|
-
> "프로젝트: mss-backend" → "회원 API 엔드포인트 목록 보여줘"
|
|
57
|
-
|
|
58
|
-
여러 프로젝트를 등록하고 Slack에서 자유롭게 전환하며 작업합니다.
|
|
15
|
+
> Claude Code CLI가 할 수 있는 건 전부 Slack에서 가능합니다. 터미널로 할 수 있는 모든 것을 Slack으로 제어할 수 있습니다.
|
|
59
16
|
|
|
60
17
|
---
|
|
61
18
|
|
|
62
|
-
##
|
|
63
|
-
|
|
64
|
-
**출퇴근 중**
|
|
65
|
-
지하철에서 Jira 티켓을 보고, Slack으로 "JIRA-123 이슈 수정해줘"라고 보내면 집에 있는 PC가 코드를 수정하고 PR을 올립니다.
|
|
66
|
-
|
|
67
|
-
**카페에서**
|
|
68
|
-
노트북 없이 스마트폰만으로 "어제 작업한 feature 브랜치 테스트 돌려봐" → 테스트 결과를 Slack으로 받습니다.
|
|
19
|
+
## 빠른 시작
|
|
69
20
|
|
|
70
|
-
|
|
71
|
-
업무용 PC가 회사에 있어도, Slack으로 회사 PC의 Claude Code를 제어하여 개발합니다.
|
|
21
|
+
**사전 준비:** Node.js 20+, [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)
|
|
72
22
|
|
|
73
|
-
|
|
74
|
-
|
|
23
|
+
```bash
|
|
24
|
+
# 설치
|
|
25
|
+
npm install -g choavis-agent
|
|
75
26
|
|
|
76
|
-
|
|
27
|
+
# 대화형 설정 (Slack 앱 생성 안내 + .env 생성)
|
|
28
|
+
choavis-agent onboard
|
|
77
29
|
|
|
78
|
-
|
|
30
|
+
# 실행
|
|
31
|
+
choavis-agent start
|
|
32
|
+
```
|
|
79
33
|
|
|
80
|
-
|
|
81
|
-
|------|------|
|
|
82
|
-
| 대화형 코딩 | 자연어로 코드 작성, 수정, 리팩토링 지시 |
|
|
83
|
-
| 스레드 세션 | Slack 스레드별 독립 세션, 컨텍스트 유지 |
|
|
84
|
-
| 스트리밍 응답 | 실시간 진행 상황 표시 |
|
|
85
|
-
| 도구 승인 | Slack 버튼으로 도구 사용 승인/거부 (PreToolUse Hook 기반) |
|
|
86
|
-
| 에러 자동 복구 | 컨텍스트 오버플로우 자동 압축, 세션 오류 자동 리셋 |
|
|
87
|
-
| 사용량 추적 | 토큰 사용량, 비용 추정, 사용자별 통계 |
|
|
88
|
-
| 모델 선택 | opus, sonnet, haiku 또는 직접 모델명 입력 |
|
|
34
|
+
> **Slack 앱 설정이 처음이라면?** → [Slack 앱 설정 가이드](docs/slack-app-setup.md)
|
|
89
35
|
|
|
90
36
|
---
|
|
91
37
|
|
|
92
|
-
##
|
|
38
|
+
## 동작 원리
|
|
93
39
|
|
|
94
|
-
|
|
95
|
-
2. **Claude Code CLI** - [설치 가이드](https://docs.anthropic.com/en/docs/claude-code)
|
|
96
|
-
```bash
|
|
97
|
-
npm install -g @anthropic-ai/claude-code
|
|
98
|
-
```
|
|
99
|
-
3. **Slack 앱** - 아래 가이드를 따라 생성
|
|
40
|
+
Choavis Agent는 Claude Code를 subprocess로 실행하고, Slack을 UI로 연결하는 브릿지입니다. Slack 스레드 하나가 Claude Code 세션 하나에 대응하며, 각 스레드는 독립적인 컨텍스트를 유지합니다.
|
|
100
41
|
|
|
101
42
|
---
|
|
102
43
|
|
|
103
|
-
##
|
|
44
|
+
## 활용 예시
|
|
104
45
|
|
|
105
|
-
|
|
106
|
-
# 1. 설치
|
|
107
|
-
npm install -g choavis-agent
|
|
46
|
+
### 언제 어디서나 코딩
|
|
108
47
|
|
|
109
|
-
|
|
110
|
-
choavis-agent init
|
|
48
|
+
지하철, 카페, 침대 — 스마트폰에 Slack만 있으면 됩니다.
|
|
111
49
|
|
|
112
|
-
# 3. 실행
|
|
113
|
-
choavis-agent start
|
|
114
50
|
```
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
choavis-agent config
|
|
51
|
+
나: "AuthController에 JWT 리프레시 토큰 로직 추가해줘"
|
|
52
|
+
나: "테스트 돌려보고 실패하면 고쳐줘"
|
|
53
|
+
나: "커밋하고 PR 만들어줘"
|
|
119
54
|
```
|
|
120
55
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
## Slack 앱 설정 가이드
|
|
56
|
+
Claude Code를 subprocess로 호출하기 때문에 프로젝트에 정의된 **CLAUDE.md, sub agent, skill**을 그대로 활용합니다. 터미널에서 직접 `claude`를 실행하는 것과 동일한 환경입니다.
|
|
124
57
|
|
|
125
|
-
###
|
|
126
|
-
1. [Slack API](https://api.slack.com/apps)에서 **Create New App** → **From scratch**
|
|
127
|
-
2. 앱 이름 입력 (예: `Choavis Agent`), 워크스페이스 선택
|
|
58
|
+
### 여러 프로젝트를 동시에
|
|
128
59
|
|
|
129
|
-
|
|
130
|
-
1. **Settings > Socket Mode** → Enable Socket Mode
|
|
131
|
-
2. App-Level Token 생성 (이름: `socket`, Scope: `connections:write`)
|
|
132
|
-
3. 생성된 토큰 복사 (`xapp-...`) → 온보딩에서 입력
|
|
60
|
+
`$project` 커맨드로 프로젝트를 전환하면서 작업할 수 있습니다.
|
|
133
61
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
| Scope | 용도 |
|
|
138
|
-
|-------|------|
|
|
139
|
-
| `app_mentions:read` | @멘션 감지 |
|
|
140
|
-
| `channels:history` | 채널 메시지 읽기 |
|
|
141
|
-
| `chat:write` | 메시지 전송 |
|
|
142
|
-
| `files:write` | 코드 스니펫 업로드 |
|
|
143
|
-
| `groups:history` | 비공개 채널 메시지 읽기 |
|
|
144
|
-
| `im:history` | DM 메시지 읽기 |
|
|
145
|
-
| `im:write` | DM 전송 |
|
|
146
|
-
| `reactions:read` | 리액션 읽기 |
|
|
147
|
-
| `reactions:write` | 리액션 추가/제거 |
|
|
62
|
+
```
|
|
63
|
+
나: "$project api"
|
|
64
|
+
나: "주문 API에 페이지네이션 추가해줘"
|
|
148
65
|
|
|
149
|
-
|
|
150
|
-
|
|
66
|
+
나: "$project frontend"
|
|
67
|
+
나: "주문 목록 페이지에 무한 스크롤 적용해줘"
|
|
68
|
+
```
|
|
151
69
|
|
|
152
|
-
|
|
153
|
-
- `app_mention` - 채널에서 @멘션 시
|
|
154
|
-
- `message.channels` - 채널 메시지
|
|
155
|
-
- `message.groups` - 비공개 채널 메시지
|
|
156
|
-
- `message.im` - DM 메시지
|
|
70
|
+
### 개발 외 작업
|
|
157
71
|
|
|
158
|
-
|
|
159
|
-
**Settings > Install App** → Install to Workspace → 권한 승인
|
|
72
|
+
터미널로 할 수 있는 건 뭐든 가능합니다.
|
|
160
73
|
|
|
161
|
-
|
|
74
|
+
```
|
|
75
|
+
나: "Downloads 폴더에 있는 PDF 파일들 날짜별로 정리해줘"
|
|
76
|
+
나: "지난주 회의록 초안 작성해줘, 프로젝트 커밋 로그 참고해서"
|
|
77
|
+
나: "afplay로 ~/Music에 있는 mp3 틀어줘"
|
|
78
|
+
```
|
|
162
79
|
|
|
163
80
|
---
|
|
164
81
|
|
|
165
|
-
##
|
|
82
|
+
## 기능
|
|
166
83
|
|
|
167
|
-
|
|
168
|
-
|
|
84
|
+
| 기능 | 설명 |
|
|
85
|
+
|---|---|
|
|
86
|
+
| **실시간 스트리밍** | Claude가 작업하는 동안 Slack 메시지가 실시간으로 업데이트 |
|
|
87
|
+
| **스레드 = 세션** | 각 Slack 스레드가 독립적인 Claude Code 세션을 유지 |
|
|
88
|
+
| **멀티 프로젝트** | 여러 저장소를 등록하고 `$project` 커맨드로 전환 |
|
|
89
|
+
| **14개 내장 커맨드** | 중지, 재개, 압축, 모델 전환, 권한 모드 등 |
|
|
90
|
+
| **도구 승인** | `plan`/`acceptEdits` 모드에서 Slack 버튼으로 도구 사용 승인/거부 |
|
|
91
|
+
| **사용량 추적** | 사용자별 토큰 소비량 및 비용을 일/주/월 단위로 집계 |
|
|
92
|
+
| **영구 메모리** | `$memory`로 저장한 메모가 이후 모든 대화에 자동 주입 |
|
|
93
|
+
| **자동 컨텍스트 압축** | 컨텍스트 창이 가득 차면 자동으로 압축 후 계속 |
|
|
169
94
|
|
|
170
|
-
|
|
171
|
-
|--------|------|------|
|
|
172
|
-
| `$중지` | `$멈춰`, `$그만`, `$stop` | 현재 처리 중인 작업 즉시 중단 |
|
|
95
|
+
---
|
|
173
96
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
97
|
+
## 커맨드
|
|
98
|
+
|
|
99
|
+
자연어로 요청하되, 에이전트를 직접 제어할 때는 커맨드를 사용합니다.
|
|
100
|
+
|
|
101
|
+
| 커맨드 | 설명 |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `$stop` | 현재 작업 즉시 중단 |
|
|
104
|
+
| `$new` | 세션 초기화 (새 대화 시작) |
|
|
105
|
+
| `$compact` | 대화 컨텍스트 수동 압축 |
|
|
106
|
+
| `$resume <id>` | 세션 ID로 기존 세션 재연결 |
|
|
107
|
+
| `$model [이름]` | 모델 확인/변경 (`sonnet`, `opus`, `haiku`) |
|
|
108
|
+
| `$status` | 현재 설정 및 에이전트 상태 |
|
|
109
|
+
| `$sessions` | 모든 활성 세션 목록 |
|
|
110
|
+
| `$session-info` | 현재 스레드의 세션 정보 |
|
|
111
|
+
| `$usage [범위]` | 사용량 대시보드 (`me`, `week`, `month`) |
|
|
112
|
+
| `$project <이름>` | 작업 디렉토리 전환 |
|
|
113
|
+
| `$inject <id> <msg>` | 특정 세션에 메시지 주입 |
|
|
114
|
+
| `$memory <내용>` | 영구 메모리에 메모 저장 |
|
|
115
|
+
| `$permission [모드]` | 권한 모드 변경 (`bypass`, `plan`, `accept`) |
|
|
116
|
+
| `$help` | 도움말 |
|
|
179
117
|
|
|
180
118
|
---
|
|
181
119
|
|
|
182
|
-
## 설정
|
|
120
|
+
## 설정
|
|
183
121
|
|
|
184
|
-
`choavis-agent
|
|
122
|
+
`choavis-agent init`으로 `.env`를 생성하거나, 직접 환경변수를 설정합니다.
|
|
185
123
|
|
|
186
|
-
|
|
|
187
|
-
|
|
188
|
-
| `SLACK_BOT_TOKEN` |
|
|
189
|
-
| `SLACK_APP_TOKEN` |
|
|
190
|
-
| `
|
|
191
|
-
| `
|
|
192
|
-
| `
|
|
193
|
-
| `AGENT_WORK_DIR` |
|
|
194
|
-
| `
|
|
195
|
-
| `
|
|
196
|
-
| `MAX_CONCURRENT_AGENTS` | - | `5` | 동시 실행 가능한 Claude 프로세스 수 |
|
|
197
|
-
| `SESSION_DAILY_RESET_HOUR` | - | `4` | 매일 세션 자동 리셋 시각 (0-23) |
|
|
198
|
-
| `SESSION_IDLE_RESET_MINUTES` | - | `120` | 유휴 세션 자동 리셋 시간 (분) |
|
|
124
|
+
| 변수 | 필수 | 기본값 | 설명 |
|
|
125
|
+
|---|---|---|---|
|
|
126
|
+
| `SLACK_BOT_TOKEN` | ✅ | — | Bot OAuth Token (`xoxb-...`) |
|
|
127
|
+
| `SLACK_APP_TOKEN` | ✅ | — | App-Level Token (`xapp-...`) |
|
|
128
|
+
| `ALLOWED_SLACK_USERS` | ✅ | — | 허용 사용자 ID (쉼표 구분) |
|
|
129
|
+
| `CLAUDE_MODEL` | — | CLI 기본값 | 모델 (`sonnet`, `opus`, `haiku` 또는 전체 ID) |
|
|
130
|
+
| `AGENT_PERMISSION_MODE` | — | `bypassPermissions` | 권한 모드 |
|
|
131
|
+
| `AGENT_WORK_DIR` | — | 현재 디렉토리 | 기본 작업 디렉토리 |
|
|
132
|
+
| `AGENT_PROJECTS` | — | `{}` | 프로젝트 맵 (JSON). 예: `{"api":"/home/user/api"}` |
|
|
133
|
+
| `MAX_CONCURRENT_AGENTS` | — | `5` | 최대 동시 프로세스 수 |
|
|
199
134
|
|
|
200
|
-
|
|
135
|
+
<details>
|
|
136
|
+
<summary>전체 환경변수 목록</summary>
|
|
201
137
|
|
|
202
|
-
|
|
138
|
+
| 변수 | 기본값 | 설명 |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| `CLAUDE_PATH` | `claude` | Claude Code CLI 경로 |
|
|
141
|
+
| `SESSION_DAILY_RESET_HOUR` | `4` | 세션 자동 리셋 시각 (0–23) |
|
|
142
|
+
| `SESSION_IDLE_RESET_MINUTES` | `120` | 유휴 세션 리셋 시간 (분) |
|
|
143
|
+
| `SESSION_STORE_PATH` | `data/sessions.json` | 세션 데이터 저장 경로 |
|
|
144
|
+
| `USAGE_DATA_DIR` | `data/usage` | 사용량 데이터 디렉토리 |
|
|
145
|
+
| `MEMORY_DATA_DIR` | `.memory` | 메모리 파일 디렉토리 |
|
|
203
146
|
|
|
204
|
-
|
|
205
|
-
┌─────────────┐ Socket Mode ┌──────────────────┐
|
|
206
|
-
│ Slack App │◄───────────────────►│ Choavis Agent │
|
|
207
|
-
│ (UI 역할) │ │ (Node.js) │
|
|
208
|
-
│ │ 승인/거부 버튼 클릭 ├──────────────────┤
|
|
209
|
-
└─────────────┘ │ Command Router │
|
|
210
|
-
│ Session Manager │
|
|
211
|
-
사용자가 │ Queue System │
|
|
212
|
-
Slack에서 │ Memory System │
|
|
213
|
-
메시지 전송 │ Usage Tracker │
|
|
214
|
-
│ Approval Server │ ← localhost HTTP
|
|
215
|
-
└────────┬─────────┘
|
|
216
|
-
│ subprocess
|
|
217
|
-
┌────────▼─────────┐
|
|
218
|
-
│ Claude Code │
|
|
219
|
-
│ CLI (로컬) │
|
|
220
|
-
│ │
|
|
221
|
-
│ 코드 읽기/쓰기 │
|
|
222
|
-
│ 터미널 명령 실행 │
|
|
223
|
-
│ Git 작업 │
|
|
224
|
-
│ ... 무엇이든 │
|
|
225
|
-
├──────────────────┤
|
|
226
|
-
│ PreToolUse Hook │
|
|
227
|
-
│ → 승인 서버 요청 │
|
|
228
|
-
└──────────────────┘
|
|
229
|
-
```
|
|
147
|
+
</details>
|
|
230
148
|
|
|
231
|
-
|
|
232
|
-
- **코드는 내 로컬에서만** - 외부 서버 없음, Claude Code CLI가 로컬에서 직접 실행
|
|
233
|
-
- **Slack은 리모컨** - 메시지를 전달하고 결과를 보여주는 UI 역할
|
|
234
|
-
- **스레드 = 세션** - 각 Slack 스레드가 독립적인 Claude Code 세션
|
|
149
|
+
---
|
|
235
150
|
|
|
236
|
-
|
|
151
|
+
## 도구 승인
|
|
237
152
|
|
|
238
|
-
`
|
|
153
|
+
`AGENT_PERMISSION_MODE`가 `plan` 또는 `acceptEdits`일 때, Claude Code는 도구 사용 전에 Slack 버튼으로 승인을 요청합니다.
|
|
239
154
|
|
|
240
155
|
```
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
6. 승인 서버 → hook에 결과 반환
|
|
247
|
-
7. Claude 계속 실행 또는 도구 스킵
|
|
156
|
+
🔧 도구 사용 승인 요청
|
|
157
|
+
도구: Bash
|
|
158
|
+
내용: git commit -m "fix: null 체크 추가"
|
|
159
|
+
|
|
160
|
+
[승인] [항상 허용] [거부]
|
|
248
161
|
```
|
|
249
162
|
|
|
250
|
-
- Read, Glob, Grep
|
|
251
|
-
- 2분 내 응답 없으면 자동 거부
|
|
252
|
-
- 승인 서버는 `127.0.0.1`에만 바인딩
|
|
163
|
+
- 읽기 전용 도구(Read, Glob, Grep)는 자동 허용
|
|
164
|
+
- 2분 내 응답 없으면 자동 거부
|
|
165
|
+
- 승인 서버는 `127.0.0.1`에만 바인딩 — 외부 접근 불가
|
|
253
166
|
|
|
254
167
|
---
|
|
255
168
|
|
|
256
169
|
## 개발
|
|
257
170
|
|
|
258
171
|
```bash
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
#
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
#
|
|
266
|
-
npm test
|
|
172
|
+
git clone https://github.com/musinsa/choavis-agent.git
|
|
173
|
+
cd choavis-agent
|
|
174
|
+
npm install
|
|
175
|
+
cp .env.example .env # 환경변수 설정
|
|
176
|
+
|
|
177
|
+
npm run dev # 개발 모드 (tsx)
|
|
178
|
+
npm run build # 빌드
|
|
179
|
+
npm test # 테스트
|
|
267
180
|
```
|
|
268
181
|
|
|
182
|
+
기여를 환영합니다. 큰 PR 전에 이슈를 먼저 열어주세요.
|
|
183
|
+
|
|
269
184
|
---
|
|
270
185
|
|
|
271
186
|
## 라이선스
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Adapter
|
|
3
|
+
*
|
|
4
|
+
* Claude Code의 StreamEvent를 통합 SessionEvent로 변환하는 어댑터.
|
|
5
|
+
* runner.ts의 raw NDJSON 스트림을 받아 protocol.ts의 SessionEvent로 emit.
|
|
6
|
+
*/
|
|
7
|
+
import type { StreamEvent } from '../runner.js';
|
|
8
|
+
import type { SessionEvent } from '../protocol.js';
|
|
9
|
+
/**
|
|
10
|
+
* Claude Code의 StreamEvent async generator를 SessionEvent로 변환
|
|
11
|
+
*/
|
|
12
|
+
export declare function adaptClaude(rawStream: AsyncGenerator<StreamEvent>): AsyncGenerator<SessionEvent>;
|
|
13
|
+
//# sourceMappingURL=claude.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../../src/agent/adapters/claude.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAgC,MAAM,gBAAgB,CAAC;AAiEjF;;GAEG;AACH,wBAAuB,WAAW,CAChC,SAAS,EAAE,cAAc,CAAC,WAAW,CAAC,GACrC,cAAc,CAAC,YAAY,CAAC,CA2J9B"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Adapter
|
|
3
|
+
*
|
|
4
|
+
* Claude Code의 StreamEvent를 통합 SessionEvent로 변환하는 어댑터.
|
|
5
|
+
* runner.ts의 raw NDJSON 스트림을 받아 protocol.ts의 SessionEvent로 emit.
|
|
6
|
+
*/
|
|
7
|
+
import { createLogger } from '../../utils/logger.js';
|
|
8
|
+
const log = createLogger('adapter:claude');
|
|
9
|
+
/** 파일 관련 도구에서 경로와 액션을 추출 */
|
|
10
|
+
function extractFileInfo(toolName, toolInput) {
|
|
11
|
+
const filePath = toolInput.file_path ||
|
|
12
|
+
toolInput.notebook_path ||
|
|
13
|
+
null;
|
|
14
|
+
if (!filePath)
|
|
15
|
+
return null;
|
|
16
|
+
const actionMap = {
|
|
17
|
+
Read: 'read',
|
|
18
|
+
Write: 'write',
|
|
19
|
+
Edit: 'edit',
|
|
20
|
+
MultiEdit: 'edit',
|
|
21
|
+
NotebookEdit: 'edit',
|
|
22
|
+
};
|
|
23
|
+
const action = actionMap[toolName];
|
|
24
|
+
if (!action)
|
|
25
|
+
return null;
|
|
26
|
+
return { path: filePath, action };
|
|
27
|
+
}
|
|
28
|
+
/** tool use 이벤트에서 도구 이름과 입력을 추출 (여러 형식 지원) */
|
|
29
|
+
function extractToolUse(raw) {
|
|
30
|
+
// 형식 1: type === 'tool_use' (tool_name/tool_input)
|
|
31
|
+
if (raw.type === 'tool_use') {
|
|
32
|
+
const toolName = raw.tool_name || raw.name || 'unknown';
|
|
33
|
+
const toolInput = (raw.tool_input || raw.input || {});
|
|
34
|
+
return { toolName, toolInput };
|
|
35
|
+
}
|
|
36
|
+
// 형식 2: content_block_start + content_block.type === 'tool_use'
|
|
37
|
+
if (raw.type === 'content_block_start') {
|
|
38
|
+
const block = raw.content_block;
|
|
39
|
+
if (block?.type === 'tool_use' && block.name) {
|
|
40
|
+
return {
|
|
41
|
+
toolName: block.name,
|
|
42
|
+
toolInput: (block.input || {}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
/** 에러 이벤트에서 텍스트 메시지를 추출 */
|
|
49
|
+
function extractErrorText(event) {
|
|
50
|
+
const content = event.message?.content;
|
|
51
|
+
if (!Array.isArray(content))
|
|
52
|
+
return 'Unknown error';
|
|
53
|
+
return content
|
|
54
|
+
.filter((c) => c.type === 'text')
|
|
55
|
+
.map((c) => c.text || '')
|
|
56
|
+
.join('');
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Claude Code의 StreamEvent async generator를 SessionEvent로 변환
|
|
60
|
+
*/
|
|
61
|
+
export async function* adaptClaude(rawStream) {
|
|
62
|
+
let didEmitTurnStart = false;
|
|
63
|
+
for await (const raw of rawStream) {
|
|
64
|
+
const ts = Date.now();
|
|
65
|
+
// system/init → start
|
|
66
|
+
if (raw.type === 'system' && raw.subtype === 'init') {
|
|
67
|
+
yield {
|
|
68
|
+
type: 'start',
|
|
69
|
+
timestamp: ts,
|
|
70
|
+
sessionId: raw.session_id,
|
|
71
|
+
_raw: raw,
|
|
72
|
+
};
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
// assistant → turn-start (첫 번째만) + text(replace: true)
|
|
76
|
+
if (raw.type === 'assistant') {
|
|
77
|
+
if (!didEmitTurnStart) {
|
|
78
|
+
didEmitTurnStart = true;
|
|
79
|
+
yield { type: 'turn-start', timestamp: ts };
|
|
80
|
+
}
|
|
81
|
+
const msg = raw.message;
|
|
82
|
+
if (msg?.content && Array.isArray(msg.content)) {
|
|
83
|
+
const fullText = msg.content
|
|
84
|
+
.filter((block) => block.type === 'text' && block.text)
|
|
85
|
+
.map((block) => block.text)
|
|
86
|
+
.join('');
|
|
87
|
+
if (fullText) {
|
|
88
|
+
yield {
|
|
89
|
+
type: 'text',
|
|
90
|
+
timestamp: ts,
|
|
91
|
+
text: { content: fullText, replace: true },
|
|
92
|
+
_raw: raw,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// assistant 메시지 content에서 tool_use 블록도 추출
|
|
96
|
+
for (const block of msg.content) {
|
|
97
|
+
if (block.type === 'tool_use' && block.name) {
|
|
98
|
+
const toolInput = (block.input || {});
|
|
99
|
+
yield {
|
|
100
|
+
type: 'tool-call-start',
|
|
101
|
+
timestamp: ts,
|
|
102
|
+
tool: { toolName: block.name, toolInput },
|
|
103
|
+
};
|
|
104
|
+
const fileInfo = extractFileInfo(block.name, toolInput);
|
|
105
|
+
if (fileInfo) {
|
|
106
|
+
yield { type: 'file', timestamp: ts, file: fileInfo };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// content_block_delta → text(replace: false)
|
|
114
|
+
if (raw.type === 'content_block_delta' && raw.delta?.text) {
|
|
115
|
+
if (!didEmitTurnStart) {
|
|
116
|
+
didEmitTurnStart = true;
|
|
117
|
+
yield { type: 'turn-start', timestamp: ts };
|
|
118
|
+
}
|
|
119
|
+
yield {
|
|
120
|
+
type: 'text',
|
|
121
|
+
timestamp: ts,
|
|
122
|
+
text: { content: raw.delta.text, replace: false },
|
|
123
|
+
_raw: raw,
|
|
124
|
+
};
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
// tool_use → tool-call-start + optional file
|
|
128
|
+
// Claude Code stream-json은 여러 형식으로 tool use를 보낼 수 있음:
|
|
129
|
+
// 1. type: 'tool_use' (tool_name/tool_input 또는 name/input)
|
|
130
|
+
// 2. type: 'content_block_start' + content_block.type === 'tool_use'
|
|
131
|
+
{
|
|
132
|
+
const toolInfo = extractToolUse(raw);
|
|
133
|
+
if (toolInfo) {
|
|
134
|
+
yield {
|
|
135
|
+
type: 'tool-call-start',
|
|
136
|
+
timestamp: ts,
|
|
137
|
+
tool: toolInfo,
|
|
138
|
+
_raw: raw,
|
|
139
|
+
};
|
|
140
|
+
// 파일 관련 도구면 file 이벤트도 emit
|
|
141
|
+
const fileInfo = extractFileInfo(toolInfo.toolName, toolInfo.toolInput);
|
|
142
|
+
if (fileInfo) {
|
|
143
|
+
yield {
|
|
144
|
+
type: 'file',
|
|
145
|
+
timestamp: ts,
|
|
146
|
+
file: fileInfo,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// result → turn-end
|
|
153
|
+
if (raw.type === 'result') {
|
|
154
|
+
const turnEnd = {
|
|
155
|
+
type: 'turn-end',
|
|
156
|
+
timestamp: ts,
|
|
157
|
+
sessionId: raw.session_id,
|
|
158
|
+
turn: {
|
|
159
|
+
result: raw.result,
|
|
160
|
+
},
|
|
161
|
+
_raw: raw,
|
|
162
|
+
};
|
|
163
|
+
if (raw.usage) {
|
|
164
|
+
turnEnd.turn.usage = {
|
|
165
|
+
inputTokens: raw.usage.input_tokens || 0,
|
|
166
|
+
outputTokens: raw.usage.output_tokens || 0,
|
|
167
|
+
model: raw.model,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
yield turnEnd;
|
|
171
|
+
// 다음 턴을 위해 리셋
|
|
172
|
+
didEmitTurnStart = false;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
// error → service
|
|
176
|
+
if (raw.type === 'error') {
|
|
177
|
+
const message = extractErrorText(raw);
|
|
178
|
+
const errorType = raw.errorType;
|
|
179
|
+
const retryable = errorType === 'transient';
|
|
180
|
+
yield {
|
|
181
|
+
type: 'service',
|
|
182
|
+
timestamp: ts,
|
|
183
|
+
service: {
|
|
184
|
+
message,
|
|
185
|
+
severity: 'error',
|
|
186
|
+
errorType,
|
|
187
|
+
retryable,
|
|
188
|
+
},
|
|
189
|
+
_raw: raw,
|
|
190
|
+
};
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
// 그 외 이벤트 디버그 로그 (tool_use 관련 이벤트를 놓치고 있는지 확인용)
|
|
194
|
+
if (raw.type !== 'content_block_delta' && raw.type !== 'content_block_stop') {
|
|
195
|
+
log.info('unhandled event', { type: raw.type, subtype: raw.subtype });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../../src/agent/adapters/claude.ts"],"names":[],"mappings":"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"}
|