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.
Files changed (131) hide show
  1. package/.env.example +3 -2
  2. package/README.md +120 -205
  3. package/dist/agent/adapters/claude.d.ts +13 -0
  4. package/dist/agent/adapters/claude.d.ts.map +1 -0
  5. package/dist/agent/adapters/claude.js +199 -0
  6. package/dist/agent/adapters/claude.js.map +1 -0
  7. package/dist/agent/protocol.d.ts +56 -0
  8. package/dist/agent/protocol.d.ts.map +1 -0
  9. package/dist/agent/protocol.js +9 -0
  10. package/dist/agent/protocol.js.map +1 -0
  11. package/dist/agent/queue.d.ts.map +1 -1
  12. package/dist/agent/queue.js +4 -2
  13. package/dist/agent/queue.js.map +1 -1
  14. package/dist/agent/runner.d.ts +8 -0
  15. package/dist/agent/runner.d.ts.map +1 -1
  16. package/dist/agent/runner.js +13 -39
  17. package/dist/agent/runner.js.map +1 -1
  18. package/dist/agent/session.d.ts +27 -1
  19. package/dist/agent/session.d.ts.map +1 -1
  20. package/dist/agent/session.js +79 -3
  21. package/dist/agent/session.js.map +1 -1
  22. package/dist/agent/usage.d.ts +8 -5
  23. package/dist/agent/usage.d.ts.map +1 -1
  24. package/dist/agent/usage.js +21 -0
  25. package/dist/agent/usage.js.map +1 -1
  26. package/dist/approval/server.d.ts +11 -1
  27. package/dist/approval/server.d.ts.map +1 -1
  28. package/dist/approval/server.js +46 -4
  29. package/dist/approval/server.js.map +1 -1
  30. package/dist/approval/setup.d.ts +5 -0
  31. package/dist/approval/setup.d.ts.map +1 -1
  32. package/dist/approval/setup.js +31 -0
  33. package/dist/approval/setup.js.map +1 -1
  34. package/dist/cli/config.d.ts.map +1 -1
  35. package/dist/cli/config.js +18 -8
  36. package/dist/cli/config.js.map +1 -1
  37. package/dist/cli/onboard.d.ts.map +1 -1
  38. package/dist/cli/onboard.js +299 -51
  39. package/dist/cli/onboard.js.map +1 -1
  40. package/dist/cli.js +10 -5
  41. package/dist/cli.js.map +1 -1
  42. package/dist/config.d.ts +5 -0
  43. package/dist/config.d.ts.map +1 -1
  44. package/dist/config.js +9 -1
  45. package/dist/config.js.map +1 -1
  46. package/dist/index.js +2 -2
  47. package/dist/index.js.map +1 -1
  48. package/dist/slack/commands/compact.js +2 -2
  49. package/dist/slack/commands/compact.js.map +1 -1
  50. package/dist/slack/commands/help.d.ts.map +1 -1
  51. package/dist/slack/commands/help.js +16 -11
  52. package/dist/slack/commands/help.js.map +1 -1
  53. package/dist/slack/commands/index.d.ts.map +1 -1
  54. package/dist/slack/commands/index.js +26 -0
  55. package/dist/slack/commands/index.js.map +1 -1
  56. package/dist/slack/commands/inject.d.ts +3 -0
  57. package/dist/slack/commands/inject.d.ts.map +1 -0
  58. package/dist/slack/commands/inject.js +44 -0
  59. package/dist/slack/commands/inject.js.map +1 -0
  60. package/dist/slack/commands/memory.js +7 -7
  61. package/dist/slack/commands/memory.js.map +1 -1
  62. package/dist/slack/commands/model.d.ts +3 -0
  63. package/dist/slack/commands/model.d.ts.map +1 -0
  64. package/dist/slack/commands/model.js +43 -0
  65. package/dist/slack/commands/model.js.map +1 -0
  66. package/dist/slack/commands/new.js +1 -1
  67. package/dist/slack/commands/new.js.map +1 -1
  68. package/dist/slack/commands/permission.d.ts +3 -0
  69. package/dist/slack/commands/permission.d.ts.map +1 -0
  70. package/dist/slack/commands/permission.js +51 -0
  71. package/dist/slack/commands/permission.js.map +1 -0
  72. package/dist/slack/commands/project.js +2 -2
  73. package/dist/slack/commands/project.js.map +1 -1
  74. package/dist/slack/commands/resume.d.ts +3 -0
  75. package/dist/slack/commands/resume.d.ts.map +1 -0
  76. package/dist/slack/commands/resume.js +93 -0
  77. package/dist/slack/commands/resume.js.map +1 -0
  78. package/dist/slack/commands/session-info.d.ts +3 -0
  79. package/dist/slack/commands/session-info.d.ts.map +1 -0
  80. package/dist/slack/commands/session-info.js +33 -0
  81. package/dist/slack/commands/session-info.js.map +1 -0
  82. package/dist/slack/commands/sessions.d.ts +3 -0
  83. package/dist/slack/commands/sessions.d.ts.map +1 -0
  84. package/dist/slack/commands/sessions.js +54 -0
  85. package/dist/slack/commands/sessions.js.map +1 -0
  86. package/dist/slack/commands/status.d.ts.map +1 -1
  87. package/dist/slack/commands/status.js +7 -2
  88. package/dist/slack/commands/status.js.map +1 -1
  89. package/dist/slack/commands/usage.d.ts +5 -0
  90. package/dist/slack/commands/usage.d.ts.map +1 -1
  91. package/dist/slack/commands/usage.js +93 -12
  92. package/dist/slack/commands/usage.js.map +1 -1
  93. package/dist/slack/context.d.ts +11 -8
  94. package/dist/slack/context.d.ts.map +1 -1
  95. package/dist/slack/context.js +36 -14
  96. package/dist/slack/context.js.map +1 -1
  97. package/dist/slack/file-downloader.d.ts +18 -0
  98. package/dist/slack/file-downloader.d.ts.map +1 -0
  99. package/dist/slack/file-downloader.js +196 -0
  100. package/dist/slack/file-downloader.js.map +1 -0
  101. package/dist/slack/formatter.d.ts +0 -4
  102. package/dist/slack/formatter.d.ts.map +1 -1
  103. package/dist/slack/formatter.js +0 -8
  104. package/dist/slack/formatter.js.map +1 -1
  105. package/dist/slack/handlers.d.ts +3 -4
  106. package/dist/slack/handlers.d.ts.map +1 -1
  107. package/dist/slack/handlers.js +372 -259
  108. package/dist/slack/handlers.js.map +1 -1
  109. package/dist/slack/link-resolver.d.ts +24 -0
  110. package/dist/slack/link-resolver.d.ts.map +1 -0
  111. package/dist/slack/link-resolver.js +52 -0
  112. package/dist/slack/link-resolver.js.map +1 -0
  113. package/dist/slack/middleware.d.ts +17 -0
  114. package/dist/slack/middleware.d.ts.map +1 -0
  115. package/dist/slack/middleware.js +73 -0
  116. package/dist/slack/middleware.js.map +1 -0
  117. package/dist/slack/slack-retry.d.ts +18 -0
  118. package/dist/slack/slack-retry.d.ts.map +1 -0
  119. package/dist/slack/slack-retry.js +71 -0
  120. package/dist/slack/slack-retry.js.map +1 -0
  121. package/dist/slack/tool-formatter.d.ts +18 -0
  122. package/dist/slack/tool-formatter.d.ts.map +1 -0
  123. package/dist/slack/tool-formatter.js +154 -0
  124. package/dist/slack/tool-formatter.js.map +1 -0
  125. package/dist/slack/types.d.ts +22 -0
  126. package/dist/slack/types.d.ts.map +1 -0
  127. package/dist/slack/types.js +2 -0
  128. package/dist/slack/types.js.map +1 -0
  129. package/package.json +10 -10
  130. package/scripts/pretool-hook.mjs +0 -0
  131. 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
- # ALLOWED_SLACK_USERS=U12345678,U87654321
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
- Choavis Agent는 로컬 PC에 설치된 [Claude Code](https://docs.anthropic.com/en/docs/claude-code)를 Slack을 통해 제어하는 개인 AI 코딩 에이전트입니다.
5
+ [![npm version](https://img.shields.io/npm/v/choavis-agent)](https://www.npmjs.com/package/choavis-agent)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![Node.js Version](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org/)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue)](https://www.typescriptlang.org/)
6
9
 
7
- 카페에서 커피를 마시며 스마트폰으로 Slack 메시지 하나를 보내면, 집에 있는 내 개발 머신의 Claude Code 코드를 작성하고, 테스트를 실행하고, Git에 커밋합니다. 출퇴근 지하철에서 버그 리포트를 보고, Slack에서 바로 수정을 지시할 수 있습니다.
10
+ Choavis Agent는 로컬 [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)를 Slack과 연결합니다. 터미널 앞에 앉아 있지 않아도 Slack 메시지 하나로 로컬 머신에서 개발 작업을 수행할 수 있습니다.
8
11
 
9
- > [OpenClaw](https://github.com/anthropics/claude-code)의 개념에서 영감을 받았습니다.
10
- > 외부 서버에 코드를 올리는 보안 리스크 없이, **내 로컬 머신에서 직접 실행**되는 구조로 설계했습니다.
12
+ <!-- 데모 GIF가 있다면 여기에 추가 -->
13
+ <!-- ![demo](docs/assets/demo.gif) -->
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
- 팀원들의 Slack ID를 등록하면, 하나의 에이전트를 팀이 함께 사용할 수 있습니다. 각자의 스레드에서 독립적인 세션으로 작업합니다.
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
- 1. **Node.js 20 이상** - [다운로드](https://nodejs.org/)
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
- ```bash
106
- # 1. 설치
107
- npm install -g choavis-agent
46
+ ### 언제 어디서나 코딩
108
47
 
109
- # 2. 설정 (대화형 온보딩)
110
- choavis-agent init
48
+ 지하철, 카페, 침대 스마트폰에 Slack만 있으면 됩니다.
111
49
 
112
- # 3. 실행
113
- choavis-agent start
114
50
  ```
115
-
116
- 설정을 변경하고 싶다면:
117
- ```bash
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
- ### 1. 생성
126
- 1. [Slack API](https://api.slack.com/apps)에서 **Create New App** → **From scratch**
127
- 2. 앱 이름 입력 (예: `Choavis Agent`), 워크스페이스 선택
58
+ ### 여러 프로젝트를 동시에
128
59
 
129
- ### 2. Socket Mode 활성화
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
- ### 3. 봇 권한 설정
135
- **Features > OAuth & Permissions > Bot Token Scopes**에 추가:
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
- ### 4. 이벤트 구독
150
- **Features > Event Subscriptions** Enable Events
66
+ 나: "$project frontend"
67
+ 나: "주문 목록 페이지에 무한 스크롤 적용해줘"
68
+ ```
151
69
 
152
- **Subscribe to bot events:**
153
- - `app_mention` - 채널에서 @멘션 시
154
- - `message.channels` - 채널 메시지
155
- - `message.groups` - 비공개 채널 메시지
156
- - `message.im` - DM 메시지
70
+ ### 개발 작업
157
71
 
158
- ### 5. 설치
159
- **Settings > Install App** → Install to Workspace → 권한 승인
72
+ 터미널로 있는 건 뭐든 가능합니다.
160
73
 
161
- Bot User OAuth Token 복사 (`xoxb-...`) → 온보딩에서 입력
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 config`로 변경하거나, `.env` 파일을 직접 수정할 수 있습니다.
122
+ `choavis-agent init`으로 `.env`를 생성하거나, 직접 환경변수를 설정합니다.
185
123
 
186
- | 환경변수 | 필수 | 기본값 | 설명 |
187
- |----------|------|--------|------|
188
- | `SLACK_BOT_TOKEN` | O | - | Slack Bot OAuth Token (`xoxb-...`) |
189
- | `SLACK_APP_TOKEN` | O | - | Slack App-Level Token (`xapp-...`) |
190
- | `CLAUDE_MODEL` | - | *(Claude Code 기본값)* | Claude 모델 (opus, sonnet, haiku 또는 전체 모델명) |
191
- | `AGENT_PERMISSION_MODE` | - | `plan` | Claude Code 권한 모드 |
192
- | `ALLOWED_SLACK_USERS` | - | *(전체 허용)* | 허용 사용자 ID (쉼표 구분) |
193
- | `AGENT_WORK_DIR` | - | 현재 디렉토리 | Claude Code 작업 디렉토리 |
194
- | `CLAUDE_PATH` | - | `claude` | Claude CLI 경로 |
195
- | `AGENT_PROJECTS` | - | `{}` | 프로젝트 레지스트리 (JSON) |
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
- `bypassPermissions`가 아닌 퍼미션 모드에서는 Claude Code 도구(Bash, Write 등)를 사용하기 전에 Slack 버튼으로 승인을 요청합니다.
153
+ `AGENT_PERMISSION_MODE`가 `plan` 또는 `acceptEdits`일 때, Claude Code 도구 사용 전에 Slack 버튼으로 승인을 요청합니다.
239
154
 
240
155
  ```
241
- 1. Claude가 도구 사용 시도
242
- 2. PreToolUse Hook 발동 → hook 스크립트 실행
243
- 3. hook localhost 승인 서버에 HTTP 요청 (long-poll)
244
- 4. 승인 서버 → Slack 스레드에 [✅ 승인] [❌ 거부] 버튼 전송
245
- 5. 사용자가 버튼 클릭
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
- # 개발 모드 (tsx로 즉시 실행)
260
- npm run dev
261
-
262
- # 빌드
263
- npm run build
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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"}