nworks 0.7.0 → 1.0.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 CHANGED
@@ -1,85 +1,141 @@
1
1
  # nworks
2
2
 
3
- NAVER WORKS CLI — built for humans and AI agents.
3
+ [![npm version](https://img.shields.io/npm/v/nworks.svg)](https://www.npmjs.com/package/nworks)
4
+ [![license](https://img.shields.io/npm/l/nworks.svg)](LICENSE)
5
+ [![npm downloads](https://img.shields.io/npm/dm/nworks.svg)](https://www.npmjs.com/package/nworks)
4
6
 
5
- ## Install
7
+ First full MCP server for NAVER WORKS.
8
+ NAVER WORKS API를 스크립트나 AI 에이전트에서 쓰기 쉽게 만든 CLI + MCP 서버입니다.
9
+
10
+ **Automate messages, calendar, drive, mail, tasks, and boards — from CLI or AI agents.**
11
+
12
+ ## Quickstart
6
13
 
7
14
  ```bash
8
- npx nworks
15
+ npm install -g nworks
16
+ nworks login --user
17
+ nworks calendar list
18
+ ```
19
+
20
+ ### AI 에이전트가 실제로 이렇게 씁니다
21
+
9
22
  ```
23
+ User: 오늘 일정 알려줘
24
+
25
+ Claude → nworks_calendar_list
26
+ → 3건: 스탠드업(10:00), 점심미팅(12:00), 코드리뷰(15:00)
10
27
 
11
- Or install globally:
28
+ User: 채널에 배포 완료 메시지 보내줘
29
+
30
+ Claude → nworks_message_send
31
+ { "channel": "C001", "text": "v1.2.0 배포 완료" }
32
+ → Message sent
33
+ ```
34
+
35
+ ## Install
12
36
 
13
37
  ```bash
14
- npm install -g nworks
38
+ npx nworks # 바로 실행
39
+ npm install -g nworks # 글로벌 설치
15
40
  ```
16
41
 
17
- ## Quick Start
42
+ ## 로그인
18
43
 
19
44
  ```bash
20
- # 서비스 계정 로그인
21
- nworks login \
22
- --client-id <CLIENT_ID> \
23
- --client-secret <CLIENT_SECRET> \
24
- --service-account <SERVICE_ACCOUNT> \
25
- --private-key <PATH_TO_KEY> \
26
- --bot-id <BOT_ID>
27
-
28
- # User OAuth 로그인 (캘린더, 드라이브 등 사용자 API용)
45
+ # User OAuth (캘린더, 드라이브, 메일, 할 일, 게시판)
29
46
  nworks login --user --scope "calendar,calendar.read,file,mail,task,board,user.read"
30
47
 
48
+ # 봇 메시지 전송이 필요한 경우 (Service Account)
49
+ nworks login
50
+
31
51
  # 인증 확인
32
52
  nworks whoami
33
53
 
34
- # 메시지 전송
35
- nworks message send --to <userId> --text "배포 완료했습니다"
54
+ # 로그아웃
55
+ nworks logout
56
+ ```
36
57
 
37
- # 조직 구성원 조회
38
- nworks directory members
58
+ > `nworks login --user`는 CLIENT_ID + CLIENT_SECRET만 있으면 됩니다. 환경변수나 기존 설정에 이미 있는 값은 다시 물어보지 않습니다.
39
59
 
40
- # 오늘 일정 조회
41
- nworks calendar list
60
+ > **Developer Console 설정**: User OAuth를 사용하려면 [Developer Console](https://dev.worksmobile.com)에서 Redirect URL에 `http://localhost:9876/callback`을 등록해야 합니다.
42
61
 
43
- # 일정 생성
44
- nworks calendar create --title "회의" --start "2026-03-14T14:00+09:00" --end "2026-03-14T15:00+09:00"
62
+ ---
45
63
 
46
- # 드라이브 파일 목록
47
- nworks drive list
64
+ ## MCP 서버 (AI 에이전트 연동)
48
65
 
49
- # 파일 업로드
50
- nworks drive upload --file ./report.pdf
66
+ Claude Desktop, Cursor 등에서 MCP server로 NAVER WORKS 22개 도구를 사용할 수 있습니다.
67
+ 메시지 전송, 일정 관리, 파일 업로드, 메일, 할 일, 게시판까지 — AI 에이전트가 NAVER WORKS 워크플로우를 자동화합니다.
51
68
 
52
- # 메일 전송
53
- nworks mail send --to "user@example.com" --subject "제목" --body "내용"
69
+ ### 설정
54
70
 
55
- # 받은편지함 조회
56
- nworks mail list
71
+ 먼저 로그인합니다:
57
72
 
58
- # 할 일 목록
59
- nworks task list
60
-
61
- # 할 일 생성
62
- nworks task create --title "코드 리뷰" --body "PR #382 리뷰"
73
+ ```bash
74
+ nworks login --user --scope "calendar,calendar.read,file,mail,task,board,user.read"
75
+ ```
63
76
 
64
- # 게시판 목록
65
- nworks board list
77
+ 그리고 MCP 설정에 추가합니다 (`~/.config/claude/claude_desktop_config.json`):
66
78
 
67
- # 게시판 글 작성
68
- nworks board create --board <boardId> --title "공지사항" --body "내용"
79
+ ```json
80
+ {
81
+ "mcpServers": {
82
+ "nworks": {
83
+ "command": "nworks",
84
+ "args": ["mcp"]
85
+ }
86
+ }
87
+ }
69
88
  ```
70
89
 
71
- ## CLI Commands
90
+ 끝입니다. 인증 한 번으로 22개 도구 모두 사용 가능 — 별도 env 설정이 필요 없습니다.
91
+
92
+ ### MCP 도구 목록 (22개)
93
+
94
+ | 도구 | 설명 | 필요 인증 |
95
+ |------|------|----------|
96
+ | `nworks_message_send` | 사용자/채널에 메시지 전송 | Service Account |
97
+ | `nworks_message_members` | 채널 구성원 조회 | Service Account |
98
+ | `nworks_directory_members` | 조직 구성원 조회 | Service Account |
99
+ | `nworks_calendar_list` | 캘린더 일정 조회 | User OAuth |
100
+ | `nworks_calendar_create` | 캘린더 일정 생성 | User OAuth |
101
+ | `nworks_calendar_update` | 캘린더 일정 수정 | User OAuth |
102
+ | `nworks_calendar_delete` | 캘린더 일정 삭제 | User OAuth |
103
+ | `nworks_drive_list` | 드라이브 파일/폴더 목록 | User OAuth |
104
+ | `nworks_drive_upload` | 드라이브 파일 업로드 | User OAuth |
105
+ | `nworks_drive_download` | 드라이브 파일 다운로드 | User OAuth |
106
+ | `nworks_mail_send` | 메일 전송 | User OAuth |
107
+ | `nworks_mail_list` | 메일 목록 조회 | User OAuth |
108
+ | `nworks_mail_read` | 메일 상세 조회 | User OAuth |
109
+ | `nworks_task_list` | 할 일 목록 조회 | User OAuth |
110
+ | `nworks_task_create` | 할 일 생성 | User OAuth |
111
+ | `nworks_task_update` | 할 일 수정/완료 | User OAuth |
112
+ | `nworks_task_delete` | 할 일 삭제 | User OAuth |
113
+ | `nworks_board_list` | 게시판 목록 조회 | User OAuth |
114
+ | `nworks_board_posts` | 게시판 글 목록 조회 | User OAuth |
115
+ | `nworks_board_read` | 게시판 글 상세 조회 | User OAuth |
116
+ | `nworks_board_create` | 게시판 글 작성 | User OAuth |
117
+ | `nworks_whoami` | 인증 상태 확인 | — |
118
+
119
+ ### AI 에이전트 사용 예시
72
120
 
73
- ### 인증
121
+ ```
122
+ User: 내일 오후 2시에 회의 잡고, 팀 채널에 알려줘
74
123
 
75
- ```bash
76
- nworks login [options] # 서비스 계정 로그인 (대화형 또는 플래그)
77
- nworks login --user # User OAuth 로그인 (브라우저)
78
- nworks login --user --scope calendar.read # scope 지정
79
- nworks whoami # 인증 상태 확인
80
- nworks logout # 로그아웃
124
+ Claude → nworks_calendar_create
125
+ { "summary": "회의", "start": "2026-03-15T14:00:00", "end": "2026-03-15T15:00:00" }
126
+ Event created
127
+
128
+ Claude nworks_message_send
129
+ { "channel": "C001", "text": "내일 14:00 회의가 잡혔습니다" }
130
+ → Message sent
81
131
  ```
82
132
 
133
+ ---
134
+
135
+ ## CLI 사용법
136
+
137
+ > 모든 명령어에 `--json` 지원 (파이프, 스크립트, 에이전트 파싱 용이). `message send`, `mail send`, `drive upload`는 `--dry-run`으로 실제 전송 없이 테스트 가능.
138
+
83
139
  ### 메시지 (Bot API)
84
140
 
85
141
  ```bash
@@ -107,22 +163,16 @@ nworks message members --channel <channelId>
107
163
  nworks directory members # 조직 구성원 목록
108
164
  ```
109
165
 
110
- ### 캘린더 (User OAuth 필요)
166
+ ### 캘린더 (User OAuth)
111
167
 
112
168
  ```bash
113
169
  # 오늘 일정 조회
114
170
  nworks calendar list
115
171
 
116
- # 기간 지정 (타임존 필수)
172
+ # 기간 지정
117
173
  nworks calendar list --from "2026-03-14T00:00:00+09:00" --until "2026-03-14T23:59:59+09:00"
118
174
 
119
- # 특정 사용자의 일정
120
- nworks calendar list --user <userId>
121
-
122
175
  # 일정 생성
123
- nworks calendar create --title "회의" --start "2026-03-14T14:00:00+09:00" --end "2026-03-14T15:00:00+09:00"
124
-
125
- # 초 생략 가능
126
176
  nworks calendar create --title "회의" --start "2026-03-14T14:00+09:00" --end "2026-03-14T15:00+09:00"
127
177
 
128
178
  # 장소/설명 포함
@@ -136,36 +186,22 @@ nworks calendar create --title "팀 회의" --start "2026-03-14T10:00+09:00" --e
136
186
  # 일정 수정
137
187
  nworks calendar update --id <eventId> --title "변경된 제목"
138
188
 
139
- # 시간 변경
140
- nworks calendar update --id <eventId> --start "2026-03-14T15:00+09:00" --end "2026-03-14T16:00+09:00"
141
-
142
189
  # 일정 삭제
143
190
  nworks calendar delete --id <eventId>
144
191
  ```
145
192
 
146
- > **Note**: 캘린더 API는 User OAuth가 필요합니다. 먼저 `nworks login --user --scope "calendar calendar.read"`를 실행하세요.
147
-
148
- ### 드라이브 (User OAuth 필요)
193
+ ### 드라이브 (User OAuth)
149
194
 
150
195
  ```bash
151
- # 루트 파일/폴더 목록
196
+ # 파일/폴더 목록
152
197
  nworks drive list
153
198
 
154
- # 특정 폴더 내 파일 목록
155
- nworks drive list --folder <folderId>
156
-
157
- # 페이지네이션
158
- nworks drive list --count 50 --cursor <nextCursor>
159
-
160
- # 파일 업로드 (루트)
199
+ # 파일 업로드
161
200
  nworks drive upload --file ./report.pdf
162
201
 
163
202
  # 특정 폴더에 업로드
164
203
  nworks drive upload --file ./report.pdf --folder <folderId>
165
204
 
166
- # 동일 파일명 덮어쓰기
167
- nworks drive upload --file ./report.pdf --overwrite
168
-
169
205
  # 파일 다운로드
170
206
  nworks drive download --file-id <fileId>
171
207
 
@@ -173,9 +209,7 @@ nworks drive download --file-id <fileId>
173
209
  nworks drive download --file-id <fileId> --out ./downloads --name report.pdf
174
210
  ```
175
211
 
176
- > **Note**: 드라이브 API는 User OAuth가 필요합니다. 먼저 `nworks login --user --scope file`을 실행하세요. 읽기만 필요하면 `file.read` scope로 충분합니다.
177
-
178
- ### 메일 (User OAuth 필요)
212
+ ### 메일 (User OAuth)
179
213
 
180
214
  ```bash
181
215
  # 메일 전송
@@ -194,12 +228,10 @@ nworks mail list --unread
194
228
  nworks mail read --id <mailId>
195
229
  ```
196
230
 
197
- > **Note**: 메일 API는 User OAuth가 필요합니다. 먼저 `nworks login --user --scope mail`을 실행하세요. 읽기만 필요하면 `mail.read` scope로 충분합니다.
198
-
199
- ### 할 일 (User OAuth 필요)
231
+ ### (User OAuth)
200
232
 
201
233
  ```bash
202
- # 할 일 목록 (기본 카테고리)
234
+ # 할 일 목록
203
235
  nworks task list
204
236
 
205
237
  # 미완료만 조회
@@ -214,16 +246,11 @@ nworks task create --title "배포" --due 2026-03-20
214
246
  # 할 일 완료 처리
215
247
  nworks task update --id <taskId> --status done
216
248
 
217
- # 할 일 미완료로 되돌리기
218
- nworks task update --id <taskId> --status todo
219
-
220
249
  # 할 일 삭제
221
250
  nworks task delete --id <taskId>
222
251
  ```
223
252
 
224
- > **Note**: 할 일 API는 User OAuth가 필요합니다. 먼저 `nworks login --user --scope "task user.read"`를 실행하세요. (`user.read`는 사용자 ID 조회에 필요) 읽기만 필요하면 `nworks login --user --scope "task.read user.read"`로 충분합니다.
225
-
226
- ### 게시판 (User OAuth 필요)
253
+ ### 게시판 (User OAuth)
227
254
 
228
255
  ```bash
229
256
  # 게시판 목록
@@ -242,36 +269,7 @@ nworks board create --board <boardId> --title "공지사항" --body "내용"
242
269
  nworks board create --board <boardId> --title "공지" --body "내용" --notify --no-comment
243
270
  ```
244
271
 
245
- > **Note**: 게시판 API는 User OAuth가 필요합니다. 먼저 `nworks login --user --scope board`를 실행하세요. 읽기만 필요하면 `board.read` scope로 충분합니다.
246
-
247
- ### MCP 서버
248
-
249
- ```bash
250
- nworks mcp # stdio MCP 서버 시작
251
- nworks mcp --list-tools # 등록된 tool 목록
252
- ```
253
-
254
- ## MCP 서버 연동 (Claude Desktop)
255
-
256
- `~/.config/claude/claude_desktop_config.json`:
257
-
258
- ```json
259
- {
260
- "mcpServers": {
261
- "nworks": {
262
- "command": "npx",
263
- "args": ["-y", "nworks", "mcp"],
264
- "env": {
265
- "NWORKS_CLIENT_ID": "<Client ID>",
266
- "NWORKS_CLIENT_SECRET": "<Client Secret>",
267
- "NWORKS_SERVICE_ACCOUNT": "<Service Account>",
268
- "NWORKS_PRIVATE_KEY_PATH": "<Private Key 경로>",
269
- "NWORKS_BOT_ID": "<Bot ID>"
270
- }
271
- }
272
- }
273
- }
274
- ```
272
+ ---
275
273
 
276
274
  ## OAuth Scope 설정
277
275
 
@@ -282,38 +280,29 @@ nworks mcp --list-tools # 등록된 tool 목록
282
280
  | `bot` | Bot 메시지 전송 | Service Account | `message send` |
283
281
  | `bot.read` | Bot 채널/구성원 조회 | Service Account | `message members` |
284
282
  | `user.read` | 조직 구성원 조회 | Service Account | `directory members` |
285
- | `calendar` | 캘린더 쓰기 | User OAuth | `calendar create/update/delete` (+ `calendar.read` 필요) |
283
+ | `calendar` | 캘린더 쓰기 | User OAuth | `calendar create/update/delete` |
286
284
  | `calendar.read` | 캘린더 읽기 | User OAuth | `calendar list` |
287
285
  | `file` | 드라이브 읽기/쓰기 | User OAuth | `drive list/upload/download` |
288
286
  | `file.read` | 드라이브 읽기 전용 | User OAuth | `drive list/download` |
289
287
  | `mail` | 메일 읽기/쓰기 | User OAuth | `mail send/list/read` |
290
288
  | `mail.read` | 메일 읽기 전용 | User OAuth | `mail list/read` |
291
- | `task` | 할 일 읽기/쓰기 | User OAuth | `task list/create/update/delete` (+ `user.read` 필요) |
292
- | `task.read` | 할 일 읽기 전용 | User OAuth | `task list` (+ `user.read` 필요) |
289
+ | `task` | 할 일 읽기/쓰기 | User OAuth | `task list/create/update/delete` |
290
+ | `task.read` | 할 일 읽기 전용 | User OAuth | `task list` |
293
291
  | `board` | 게시판 읽기/쓰기 | User OAuth | `board list/posts/read/create` |
294
292
  | `board.read` | 게시판 읽기 전용 | User OAuth | `board list/posts/read` |
295
293
 
296
294
  > **Tip**: scope를 변경한 후에는 토큰을 재발급해야 합니다.
297
295
  > ```bash
298
- > nworks logout && nworks login ...
296
+ > nworks logout && nworks login --user --scope "..."
299
297
  > ```
300
298
 
301
- > **Developer Console 설정**: User OAuth를 사용하려면 Developer Console에서 Redirect URL에 `http://localhost:9876/callback`을 등록해야 합니다.
302
-
303
299
  ## 사용 시나리오
304
300
 
305
301
  ### CI/CD 배포 알림
306
302
 
307
303
  ```bash
308
304
  # GitHub Actions에서 배포 완료 후 팀 채널에 알림
309
- nworks message send --channel $CHANNEL_ID --text "v${VERSION} 배포 완료"
310
- ```
311
-
312
- ### AI 에이전트 메시지
313
-
314
- ```bash
315
- # Claude Desktop / Cursor에서 MCP tool로 직접 메시지 전송
316
- # nworks mcp 서버가 nworks_message_send tool을 제공
305
+ nworks message send --channel $CHANNEL_ID --text "v${VERSION} 배포 완료"
317
306
  ```
318
307
 
319
308
  ### 팀 자동화 스크립트
@@ -321,34 +310,67 @@ nworks message send --channel $CHANNEL_ID --text "✅ v${VERSION} 배포 완료"
321
310
  ```bash
322
311
  # 매일 아침 팀원에게 리마인더 전송
323
312
  for userId in $(nworks directory members --json | jq -r '.users[].userId'); do
324
- nworks message send --to "$userId" --text "📋 오늘의 스탠드업 10시입니다"
313
+ nworks message send --to "$userId" --text "오늘의 스탠드업 10시입니다"
325
314
  done
326
315
  ```
327
316
 
328
- ## 환경 변수
317
+ ---
318
+
319
+ ## Advanced Configuration
320
+
321
+ ### 환경 변수
322
+
323
+ 환경변수로 인증 정보를 설정하면 `nworks login` 없이 바로 사용할 수 있습니다.
329
324
 
330
325
  ```bash
326
+ # 공통 (필수)
331
327
  NWORKS_CLIENT_ID= # 필수
332
328
  NWORKS_CLIENT_SECRET= # 필수
333
- NWORKS_SERVICE_ACCOUNT= # 필수
334
- NWORKS_PRIVATE_KEY_PATH= # 필수
335
- NWORKS_BOT_ID= # 필수
329
+
330
+ # 봇 메시지 전송 시에만 필요 (User OAuth만 쓰면 불필요)
331
+ NWORKS_SERVICE_ACCOUNT= # 봇 전용
332
+ NWORKS_PRIVATE_KEY_PATH= # 봇 전용
333
+ NWORKS_BOT_ID= # 봇 전용
334
+
335
+ # 선택
336
336
  NWORKS_DOMAIN_ID= # optional
337
337
  NWORKS_SCOPE= # optional (기본: bot bot.read user.read)
338
338
  NWORKS_VERBOSE=1 # optional, 디버그 로깅
339
339
  ```
340
340
 
341
- 환경변수가 설정되면 `nworks login` 없이 바로 사용 가능합니다.
341
+ ### MCP 서버에 환경 변수 직접 설정
342
+
343
+ `nworks login` 대신 환경 변수로 직접 설정할 수도 있습니다:
344
+
345
+ ```json
346
+ {
347
+ "mcpServers": {
348
+ "nworks": {
349
+ "command": "npx",
350
+ "args": ["-y", "nworks", "mcp"],
351
+ "env": {
352
+ "NWORKS_CLIENT_ID": "<Client ID>",
353
+ "NWORKS_CLIENT_SECRET": "<Client Secret>"
354
+ }
355
+ }
356
+ }
357
+ }
358
+ ```
359
+
360
+ 봇 메시지도 사용하려면 `NWORKS_SERVICE_ACCOUNT`, `NWORKS_PRIVATE_KEY_PATH`, `NWORKS_BOT_ID`도 추가합니다.
361
+
362
+ ---
342
363
 
343
364
  ## Roadmap
344
365
 
345
366
  - ~~**v0.1** — 메시지, 조직 구성원, MCP 서버~~
346
367
  - ~~**v0.2** — 캘린더 일정 조회 + User OAuth~~
347
- - ~~**v0.3** — 드라이브 파일 조회/업로드/다운로드 (`nworks drive`)~~
348
- - ~~**v0.4** — 메일 (`nworks mail send/list/read`)~~
349
- - ~~**v0.5** — 할 일 (`nworks task list/create/update/delete`)~~
350
- - ~~**v0.6** — 캘린더 쓰기 (`nworks calendar create/update/delete`)~~
351
- - ~~**v0.7** — 게시판 (`nworks board list/posts/read/create`)~~
368
+ - ~~**v0.3** — 드라이브 파일 조회/업로드/다운로드~~
369
+ - ~~**v0.4** — 메일 (`mail send/list/read`)~~
370
+ - ~~**v0.5** — 할 일 (`task list/create/update/delete`)~~
371
+ - ~~**v0.6** — 캘린더 쓰기 (`calendar create/update/delete`)~~
372
+ - ~~**v0.7** — 게시판 (`board list/posts/read/create`)~~
373
+ - **v1.0** — User OAuth 단독 로그인, MCP/CLI 인증 UX 개선, README 재구성
352
374
 
353
375
  ## License
354
376
 
package/dist/index.js CHANGED
@@ -52,20 +52,15 @@ async function ensureConfigDir() {
52
52
  function getCredentialsFromEnv() {
53
53
  const clientId = process.env["NWORKS_CLIENT_ID"];
54
54
  const clientSecret = process.env["NWORKS_CLIENT_SECRET"];
55
- const serviceAccount = process.env["NWORKS_SERVICE_ACCOUNT"];
56
- const privateKeyPath = process.env["NWORKS_PRIVATE_KEY_PATH"];
57
- const botId = process.env["NWORKS_BOT_ID"];
58
- if (clientId && clientSecret && serviceAccount && privateKeyPath && botId) {
59
- return {
60
- clientId,
61
- clientSecret,
62
- serviceAccount,
63
- privateKeyPath,
64
- botId,
65
- domainId: process.env["NWORKS_DOMAIN_ID"]
66
- };
67
- }
68
- return null;
55
+ if (!clientId || !clientSecret) return null;
56
+ return {
57
+ clientId,
58
+ clientSecret,
59
+ serviceAccount: process.env["NWORKS_SERVICE_ACCOUNT"],
60
+ privateKeyPath: process.env["NWORKS_PRIVATE_KEY_PATH"],
61
+ botId: process.env["NWORKS_BOT_ID"],
62
+ domainId: process.env["NWORKS_DOMAIN_ID"]
63
+ };
69
64
  }
70
65
  async function loadCredentials(profile = "default") {
71
66
  const envCreds = getCredentialsFromEnv();
@@ -166,6 +161,11 @@ async function clearCredentials(profile = "default") {
166
161
  import { readFile as readFile2 } from "fs/promises";
167
162
  import jwt from "jsonwebtoken";
168
163
  async function createJWT(creds) {
164
+ if (!creds.serviceAccount || !creds.privateKeyPath) {
165
+ throw new AuthError(
166
+ "Service Account credentials required for bot authentication.\nRun `nworks login` with --service-account and --private-key flags."
167
+ );
168
+ }
169
169
  const privateKey = await readFile2(creds.privateKeyPath, "utf-8");
170
170
  const now = Math.floor(Date.now() / 1e3);
171
171
  const payload = {
@@ -232,7 +232,7 @@ function buildAuthorizeUrl(clientId, scope, state) {
232
232
  });
233
233
  return `${AUTH_URL2}?${params.toString()}`;
234
234
  }
235
- async function startUserOAuthFlow(scope, profile = "default") {
235
+ async function startUserOAuthFlow(_scope, profile = "default") {
236
236
  const creds = await loadCredentials(profile);
237
237
  const code = await waitForAuthCode();
238
238
  return exchangeCodeForToken(code, creds.clientId, creds.clientSecret);
@@ -431,9 +431,31 @@ var loginCommand = new Command("login").description("Authenticate with NAVER WOR
431
431
  }
432
432
  });
433
433
  async function handleUserLogin(scope, profile, opts) {
434
- const creds = await loadCredentials(profile);
434
+ let clientId = opts.clientId;
435
+ let clientSecret = opts.clientSecret;
436
+ try {
437
+ const existing = await loadCredentials(profile);
438
+ if (!clientId) clientId = existing.clientId;
439
+ if (!clientSecret) clientSecret = existing.clientSecret;
440
+ } catch {
441
+ }
442
+ if (process.stdin.isTTY) {
443
+ if (!clientId) clientId = await prompt("Client ID: ");
444
+ if (!clientSecret) clientSecret = await prompt("Client Secret: ");
445
+ }
446
+ if (!clientId || !clientSecret) {
447
+ throw new Error(
448
+ "Client ID and Client Secret are required for User OAuth.\nUse --client-id and --client-secret flags, or set NWORKS_CLIENT_ID / NWORKS_CLIENT_SECRET env vars."
449
+ );
450
+ }
451
+ try {
452
+ const existing = await loadCredentials(profile);
453
+ await saveCredentials({ ...existing, clientId, clientSecret }, profile);
454
+ } catch {
455
+ await saveCredentials({ clientId, clientSecret }, profile);
456
+ }
435
457
  const state = randomBytes(16).toString("hex");
436
- const authorizeUrl = buildAuthorizeUrl(creds.clientId, scope, state);
458
+ const authorizeUrl = buildAuthorizeUrl(clientId, scope, state);
437
459
  console.error(`
438
460
  Opening browser for NAVER WORKS login...`);
439
461
  console.error(`If the browser does not open, visit this URL:
@@ -463,6 +485,11 @@ async function handleServiceAccountLogin(opts) {
463
485
  let botId = opts.botId;
464
486
  const domainId = opts.domainId;
465
487
  const profile = opts.profile;
488
+ if (!clientId) clientId = process.env["NWORKS_CLIENT_ID"];
489
+ if (!clientSecret) clientSecret = process.env["NWORKS_CLIENT_SECRET"];
490
+ if (!serviceAccount) serviceAccount = process.env["NWORKS_SERVICE_ACCOUNT"];
491
+ if (!privateKeyPath) privateKeyPath = process.env["NWORKS_PRIVATE_KEY_PATH"];
492
+ if (!botId) botId = process.env["NWORKS_BOT_ID"];
466
493
  if (process.stdin.isTTY) {
467
494
  if (!clientId) clientId = await prompt("Client ID: ");
468
495
  if (!clientSecret) clientSecret = await prompt("Client Secret: ");
@@ -474,7 +501,7 @@ async function handleServiceAccountLogin(opts) {
474
501
  }
475
502
  if (!clientId || !clientSecret || !serviceAccount || !privateKeyPath || !botId) {
476
503
  throw new Error(
477
- "Missing required fields. Use flags or run interactively."
504
+ "Missing required fields. Use flags, environment variables, or run interactively."
478
505
  );
479
506
  }
480
507
  if (!existsSync2(privateKeyPath)) {
@@ -527,9 +554,9 @@ var whoamiCommand = new Command3("whoami").description("Show current authenticat
527
554
  output(
528
555
  {
529
556
  profile,
530
- serviceAccount: creds.serviceAccount,
557
+ serviceAccount: creds.serviceAccount ?? "(not set)",
531
558
  clientId: creds.clientId,
532
- botId: creds.botId,
559
+ botId: creds.botId ?? "(not set)",
533
560
  domainId: creds.domainId ?? "(not set)",
534
561
  tokenValid: isValid,
535
562
  tokenExpiresAt
@@ -624,6 +651,11 @@ function buildContent(opts) {
624
651
  async function send(opts) {
625
652
  const profile = opts.profile ?? "default";
626
653
  const creds = await loadCredentials(profile);
654
+ if (!creds.botId) {
655
+ throw new Error(
656
+ "Bot ID is required for sending messages.\nRun `nworks login` with --bot-id flag to set up bot credentials."
657
+ );
658
+ }
627
659
  const content = buildContent(opts);
628
660
  const body = { content };
629
661
  if (opts.to) {
@@ -648,6 +680,11 @@ async function send(opts) {
648
680
  }
649
681
  async function listMembers(channelId, profile = "default") {
650
682
  const creds = await loadCredentials(profile);
683
+ if (!creds.botId) {
684
+ throw new Error(
685
+ "Bot ID is required for listing channel members.\nRun `nworks login` with --bot-id flag to set up bot credentials."
686
+ );
687
+ }
651
688
  const result = await request({
652
689
  method: "GET",
653
690
  path: `/bots/${creds.botId}/channels/${channelId}/members`,
@@ -855,7 +892,9 @@ async function getEvent(eventId, userId = "me", profile = "default") {
855
892
  const res = await authedFetch(url2, { method: "GET" }, profile);
856
893
  if (!res.ok) return handleError(res);
857
894
  const data = await res.json();
858
- return data.eventComponents[0];
895
+ const event = data.eventComponents[0];
896
+ if (!event) throw new ApiError("NOT_FOUND", "Event not found", 404);
897
+ return event;
859
898
  }
860
899
  async function updateEvent(opts) {
861
900
  const userId = opts.userId ?? "me";
@@ -16481,9 +16520,9 @@ function registerTools(server) {
16481
16520
  const token = await loadToken();
16482
16521
  const isValid = token ? token.expiresAt > Date.now() / 1e3 : false;
16483
16522
  const info = {
16484
- serviceAccount: creds.serviceAccount,
16523
+ serviceAccount: creds.serviceAccount ?? null,
16485
16524
  clientId: creds.clientId,
16486
- botId: creds.botId,
16525
+ botId: creds.botId ?? null,
16487
16526
  tokenValid: isValid
16488
16527
  };
16489
16528
  return {
@@ -16502,9 +16541,25 @@ function registerTools(server) {
16502
16541
 
16503
16542
  // src/mcp/server.ts
16504
16543
  async function startMcpServer() {
16544
+ try {
16545
+ await loadCredentials();
16546
+ } catch {
16547
+ console.error(
16548
+ "[nworks] Authentication required. Run: nworks login"
16549
+ );
16550
+ }
16551
+ try {
16552
+ const userToken = await loadUserToken();
16553
+ if (!userToken) {
16554
+ console.error(
16555
+ "[nworks] User OAuth not configured. Run: nworks login --user"
16556
+ );
16557
+ }
16558
+ } catch {
16559
+ }
16505
16560
  const server = new McpServer({
16506
16561
  name: "nworks",
16507
- version: "0.2.0"
16562
+ version: "1.0.0"
16508
16563
  });
16509
16564
  registerTools(server);
16510
16565
  const transport = new StdioServerTransport();