nworks 0.3.0 → 0.3.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/README.md +41 -3
- package/dist/index.js +81 -10
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +81 -10
- package/dist/mcp.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -25,8 +25,8 @@ nworks login \
|
|
|
25
25
|
--private-key <PATH_TO_KEY> \
|
|
26
26
|
--bot-id <BOT_ID>
|
|
27
27
|
|
|
28
|
-
# User OAuth 로그인 (
|
|
29
|
-
nworks login --user --scope calendar.read
|
|
28
|
+
# User OAuth 로그인 (캘린더, 드라이브 등 사용자 API용)
|
|
29
|
+
nworks login --user --scope calendar.read,file
|
|
30
30
|
|
|
31
31
|
# 인증 확인
|
|
32
32
|
nworks whoami
|
|
@@ -39,6 +39,12 @@ nworks directory members
|
|
|
39
39
|
|
|
40
40
|
# 오늘 일정 조회
|
|
41
41
|
nworks calendar list
|
|
42
|
+
|
|
43
|
+
# 드라이브 파일 목록
|
|
44
|
+
nworks drive list
|
|
45
|
+
|
|
46
|
+
# 파일 업로드
|
|
47
|
+
nworks drive upload --file ./report.pdf
|
|
42
48
|
```
|
|
43
49
|
|
|
44
50
|
## CLI Commands
|
|
@@ -95,6 +101,36 @@ nworks calendar list --user <userId>
|
|
|
95
101
|
|
|
96
102
|
> **Note**: 캘린더 API는 User OAuth가 필요합니다. 먼저 `nworks login --user --scope calendar.read`를 실행하세요.
|
|
97
103
|
|
|
104
|
+
### 드라이브 (User OAuth 필요)
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# 루트 파일/폴더 목록
|
|
108
|
+
nworks drive list
|
|
109
|
+
|
|
110
|
+
# 특정 폴더 내 파일 목록
|
|
111
|
+
nworks drive list --folder <folderId>
|
|
112
|
+
|
|
113
|
+
# 페이지네이션
|
|
114
|
+
nworks drive list --count 50 --cursor <nextCursor>
|
|
115
|
+
|
|
116
|
+
# 파일 업로드 (루트)
|
|
117
|
+
nworks drive upload --file ./report.pdf
|
|
118
|
+
|
|
119
|
+
# 특정 폴더에 업로드
|
|
120
|
+
nworks drive upload --file ./report.pdf --folder <folderId>
|
|
121
|
+
|
|
122
|
+
# 동일 파일명 덮어쓰기
|
|
123
|
+
nworks drive upload --file ./report.pdf --overwrite
|
|
124
|
+
|
|
125
|
+
# 파일 다운로드
|
|
126
|
+
nworks drive download --file-id <fileId>
|
|
127
|
+
|
|
128
|
+
# 다운로드 경로/파일명 지정
|
|
129
|
+
nworks drive download --file-id <fileId> --out ./downloads --name report.pdf
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
> **Note**: 드라이브 API는 User OAuth가 필요합니다. 먼저 `nworks login --user --scope file`을 실행하세요. 읽기만 필요하면 `file.read` scope로 충분합니다.
|
|
133
|
+
|
|
98
134
|
### MCP 서버
|
|
99
135
|
|
|
100
136
|
```bash
|
|
@@ -134,6 +170,8 @@ nworks mcp --list-tools # 등록된 tool 목록
|
|
|
134
170
|
| `bot.read` | Bot 채널/구성원 조회 | Service Account | `message members` |
|
|
135
171
|
| `user.read` | 조직 구성원 조회 | Service Account | `directory members` |
|
|
136
172
|
| `calendar.read` | 캘린더 일정 조회 | User OAuth | `calendar list` |
|
|
173
|
+
| `file` | 드라이브 읽기/쓰기 | User OAuth | `drive list/upload/download` |
|
|
174
|
+
| `file.read` | 드라이브 읽기 전용 | User OAuth | `drive list/download` |
|
|
137
175
|
|
|
138
176
|
> **Tip**: scope를 변경한 후에는 토큰을 재발급해야 합니다.
|
|
139
177
|
> ```bash
|
|
@@ -186,7 +224,7 @@ NWORKS_VERBOSE=1 # optional, 디버그 로깅
|
|
|
186
224
|
|
|
187
225
|
- ~~**v0.1** — 메시지, 조직 구성원, MCP 서버~~
|
|
188
226
|
- ~~**v0.2** — 캘린더 일정 조회 + User OAuth~~
|
|
189
|
-
-
|
|
227
|
+
- ~~**v0.3** — 드라이브 파일 조회/업로드/다운로드 (`nworks drive`)~~
|
|
190
228
|
- **v0.4** — 게시판, 메일 (`nworks board`, `nworks mail`)
|
|
191
229
|
|
|
192
230
|
## License
|
package/dist/index.js
CHANGED
|
@@ -919,6 +919,51 @@ Content-Type: application/octet-stream\r
|
|
|
919
919
|
if (!uploadRes.ok) return handleError(uploadRes);
|
|
920
920
|
return await uploadRes.json();
|
|
921
921
|
}
|
|
922
|
+
async function uploadBuffer(fileBuffer, fileName, userId = "me", folderId, overwrite = false, profile = "default") {
|
|
923
|
+
const fileSize = fileBuffer.length;
|
|
924
|
+
const base = `${BASE_URL3}/users/${userId}/drive/files`;
|
|
925
|
+
const createUrl = folderId ? `${base}/${folderId}` : base;
|
|
926
|
+
if (process.env["NWORKS_VERBOSE"] === "1") {
|
|
927
|
+
console.error(`[nworks] POST ${createUrl} (create upload URL for buffer)`);
|
|
928
|
+
}
|
|
929
|
+
const createRes = await authedFetch(
|
|
930
|
+
createUrl,
|
|
931
|
+
{
|
|
932
|
+
method: "POST",
|
|
933
|
+
headers: { "Content-Type": "application/json" },
|
|
934
|
+
body: JSON.stringify({ fileName, fileSize, overwrite })
|
|
935
|
+
},
|
|
936
|
+
profile
|
|
937
|
+
);
|
|
938
|
+
if (!createRes.ok) return handleError(createRes);
|
|
939
|
+
const { uploadUrl } = await createRes.json();
|
|
940
|
+
const boundary = `----nworks${Date.now()}`;
|
|
941
|
+
const header = Buffer.from(
|
|
942
|
+
`--${boundary}\r
|
|
943
|
+
Content-Disposition: form-data; name="Filedata"; filename="${fileName}"\r
|
|
944
|
+
Content-Type: application/octet-stream\r
|
|
945
|
+
\r
|
|
946
|
+
`
|
|
947
|
+
);
|
|
948
|
+
const footer = Buffer.from(`\r
|
|
949
|
+
--${boundary}--\r
|
|
950
|
+
`);
|
|
951
|
+
const body = Buffer.concat([header, fileBuffer, footer]);
|
|
952
|
+
if (process.env["NWORKS_VERBOSE"] === "1") {
|
|
953
|
+
console.error(`[nworks] POST ${uploadUrl} (upload buffer, ${fileSize} bytes)`);
|
|
954
|
+
}
|
|
955
|
+
const uploadRes = await authedFetch(
|
|
956
|
+
uploadUrl,
|
|
957
|
+
{
|
|
958
|
+
method: "POST",
|
|
959
|
+
headers: { "Content-Type": `multipart/form-data; boundary=${boundary}` },
|
|
960
|
+
body
|
|
961
|
+
},
|
|
962
|
+
profile
|
|
963
|
+
);
|
|
964
|
+
if (!uploadRes.ok) return handleError(uploadRes);
|
|
965
|
+
return await uploadRes.json();
|
|
966
|
+
}
|
|
922
967
|
async function downloadFile(fileId, userId = "me", profile = "default") {
|
|
923
968
|
const url2 = `${BASE_URL3}/users/${userId}/drive/files/${fileId}/download`;
|
|
924
969
|
if (process.env["NWORKS_VERBOSE"] === "1") {
|
|
@@ -14979,28 +15024,54 @@ function registerTools(server) {
|
|
|
14979
15024
|
);
|
|
14980
15025
|
server.tool(
|
|
14981
15026
|
"nworks_drive_upload",
|
|
14982
|
-
"\
|
|
15027
|
+
"\uD30C\uC77C\uC744 \uB4DC\uB77C\uC774\uBE0C\uC5D0 \uC5C5\uB85C\uB4DC\uD569\uB2C8\uB2E4 (User OAuth file scope \uD544\uC694). content(base64)\uC640 fileName\uC73C\uB85C \uC804\uB2EC\uD558\uAC70\uB098, filePath\uB85C \uB85C\uCEEC \uD30C\uC77C \uACBD\uB85C\uB97C \uC9C0\uC815\uD569\uB2C8\uB2E4. MCP \uD074\uB77C\uC774\uC5B8\uD2B8\uC5D0\uC11C\uB294 content+fileName \uBC29\uC2DD\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4.",
|
|
14983
15028
|
{
|
|
14984
|
-
|
|
15029
|
+
content: external_exports.string().optional().describe("\uC5C5\uB85C\uB4DC\uD560 \uD30C\uC77C \uB0B4\uC6A9 (base64 \uC778\uCF54\uB529). filePath \uB300\uC2E0 \uC0AC\uC6A9"),
|
|
15030
|
+
fileName: external_exports.string().optional().describe("\uD30C\uC77C\uBA85 (content \uC0AC\uC6A9 \uC2DC \uD544\uC218)"),
|
|
15031
|
+
filePath: external_exports.string().optional().describe("\uC5C5\uB85C\uB4DC\uD560 \uB85C\uCEEC \uD30C\uC77C \uACBD\uB85C (content \uB300\uC2E0 \uC0AC\uC6A9, \uB85C\uCEEC \uD658\uACBD\uC5D0\uC11C\uB9CC \uB3D9\uC791)"),
|
|
14985
15032
|
userId: external_exports.string().optional().describe("\uB300\uC0C1 \uC0AC\uC6A9\uC790 ID (\uBBF8\uC9C0\uC815 \uC2DC me)"),
|
|
14986
15033
|
folderId: external_exports.string().optional().describe("\uC5C5\uB85C\uB4DC\uD560 \uD3F4\uB354 ID (\uBBF8\uC9C0\uC815 \uC2DC \uB8E8\uD2B8)"),
|
|
14987
15034
|
overwrite: external_exports.boolean().optional().describe("\uB3D9\uC77C \uD30C\uC77C\uBA85 \uB36E\uC5B4\uC4F0\uAE30 (\uAE30\uBCF8: false)")
|
|
14988
15035
|
},
|
|
14989
|
-
async ({ filePath, userId, folderId, overwrite }) => {
|
|
15036
|
+
async ({ content, fileName, filePath, userId, folderId, overwrite }) => {
|
|
14990
15037
|
try {
|
|
14991
|
-
|
|
14992
|
-
|
|
14993
|
-
|
|
14994
|
-
|
|
14995
|
-
|
|
14996
|
-
|
|
15038
|
+
let result;
|
|
15039
|
+
if (content && fileName) {
|
|
15040
|
+
const buffer = Buffer.from(content, "base64");
|
|
15041
|
+
if (process.env["NWORKS_VERBOSE"] === "1") {
|
|
15042
|
+
console.error(`[nworks] MCP upload: fileName=${fileName}, bufferSize=${buffer.length}`);
|
|
15043
|
+
}
|
|
15044
|
+
result = await uploadBuffer(
|
|
15045
|
+
buffer,
|
|
15046
|
+
fileName,
|
|
15047
|
+
userId ?? "me",
|
|
15048
|
+
folderId,
|
|
15049
|
+
overwrite ?? false
|
|
15050
|
+
);
|
|
15051
|
+
} else if (filePath) {
|
|
15052
|
+
if (process.env["NWORKS_VERBOSE"] === "1") {
|
|
15053
|
+
console.error(`[nworks] MCP upload: filePath=${filePath}`);
|
|
15054
|
+
}
|
|
15055
|
+
result = await uploadFile(
|
|
15056
|
+
filePath,
|
|
15057
|
+
userId ?? "me",
|
|
15058
|
+
folderId,
|
|
15059
|
+
overwrite ?? false
|
|
15060
|
+
);
|
|
15061
|
+
} else {
|
|
15062
|
+
return {
|
|
15063
|
+
content: [{ type: "text", text: "Error: content+fileName \uB610\uB294 filePath \uC911 \uD558\uB098\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4. MCP \uD074\uB77C\uC774\uC5B8\uD2B8\uC5D0\uC11C\uB294 \uD30C\uC77C \uB0B4\uC6A9\uC744 base64\uB85C \uC778\uCF54\uB529\uD558\uC5EC content \uD30C\uB77C\uBBF8\uD130\uC5D0 \uC804\uB2EC\uD558\uACE0, fileName\uC5D0 \uD30C\uC77C\uBA85\uC744 \uC9C0\uC815\uD558\uC138\uC694." }],
|
|
15064
|
+
isError: true
|
|
15065
|
+
};
|
|
15066
|
+
}
|
|
14997
15067
|
return {
|
|
14998
15068
|
content: [{ type: "text", text: JSON.stringify({ success: true, ...result }) }]
|
|
14999
15069
|
};
|
|
15000
15070
|
} catch (err) {
|
|
15001
15071
|
const error48 = err;
|
|
15072
|
+
const detail = process.env["NWORKS_VERBOSE"] === "1" ? ` | stack: ${error48.stack}` : "";
|
|
15002
15073
|
return {
|
|
15003
|
-
content: [{ type: "text", text: `Error: ${error48.message}` }],
|
|
15074
|
+
content: [{ type: "text", text: `Error: ${error48.message}${detail}` }],
|
|
15004
15075
|
isError: true
|
|
15005
15076
|
};
|
|
15006
15077
|
}
|