nworks 0.6.1 → 0.7.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
@@ -26,7 +26,7 @@ nworks login \
26
26
  --bot-id <BOT_ID>
27
27
 
28
28
  # User OAuth 로그인 (캘린더, 드라이브 등 사용자 API용)
29
- nworks login --user --scope "calendar,calendar.read,file,mail,task,user.read"
29
+ nworks login --user --scope "calendar,calendar.read,file,mail,task,board,user.read"
30
30
 
31
31
  # 인증 확인
32
32
  nworks whoami
@@ -60,6 +60,12 @@ nworks task list
60
60
 
61
61
  # 할 일 생성
62
62
  nworks task create --title "코드 리뷰" --body "PR #382 리뷰"
63
+
64
+ # 게시판 목록
65
+ nworks board list
66
+
67
+ # 게시판 글 작성
68
+ nworks board create --board <boardId> --title "공지사항" --body "내용"
63
69
  ```
64
70
 
65
71
  ## CLI Commands
@@ -217,6 +223,27 @@ nworks task delete --id <taskId>
217
223
 
218
224
  > **Note**: 할 일 API는 User OAuth가 필요합니다. 먼저 `nworks login --user --scope "task user.read"`를 실행하세요. (`user.read`는 사용자 ID 조회에 필요) 읽기만 필요하면 `nworks login --user --scope "task.read user.read"`로 충분합니다.
219
225
 
226
+ ### 게시판 (User OAuth 필요)
227
+
228
+ ```bash
229
+ # 게시판 목록
230
+ nworks board list
231
+
232
+ # 게시판 글 목록
233
+ nworks board posts --board <boardId>
234
+
235
+ # 글 상세 조회
236
+ nworks board read --board <boardId> --post <postId>
237
+
238
+ # 글 작성
239
+ nworks board create --board <boardId> --title "공지사항" --body "내용"
240
+
241
+ # 알림 발송 + 댓글 비활성화
242
+ nworks board create --board <boardId> --title "공지" --body "내용" --notify --no-comment
243
+ ```
244
+
245
+ > **Note**: 게시판 API는 User OAuth가 필요합니다. 먼저 `nworks login --user --scope board`를 실행하세요. 읽기만 필요하면 `board.read` scope로 충분합니다.
246
+
220
247
  ### MCP 서버
221
248
 
222
249
  ```bash
@@ -263,6 +290,8 @@ nworks mcp --list-tools # 등록된 tool 목록
263
290
  | `mail.read` | 메일 읽기 전용 | User OAuth | `mail list/read` |
264
291
  | `task` | 할 일 읽기/쓰기 | User OAuth | `task list/create/update/delete` (+ `user.read` 필요) |
265
292
  | `task.read` | 할 일 읽기 전용 | User OAuth | `task list` (+ `user.read` 필요) |
293
+ | `board` | 게시판 읽기/쓰기 | User OAuth | `board list/posts/read/create` |
294
+ | `board.read` | 게시판 읽기 전용 | User OAuth | `board list/posts/read` |
266
295
 
267
296
  > **Tip**: scope를 변경한 후에는 토큰을 재발급해야 합니다.
268
297
  > ```bash
@@ -319,7 +348,7 @@ NWORKS_VERBOSE=1 # optional, 디버그 로깅
319
348
  - ~~**v0.4** — 메일 (`nworks mail send/list/read`)~~
320
349
  - ~~**v0.5** — 할 일 (`nworks task list/create/update/delete`)~~
321
350
  - ~~**v0.6** — 캘린더 쓰기 (`nworks calendar create/update/delete`)~~
322
- - **v0.7** — 게시판 (`nworks board list/post/read`)
351
+ - ~~**v0.7** — 게시판 (`nworks board list/posts/read/create`)~~
323
352
 
324
353
  ## License
325
354
 
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ var __export = (target, all) => {
7
7
 
8
8
  // src/index.ts
9
9
  import { createRequire } from "module";
10
- import { Command as Command11 } from "commander";
10
+ import { Command as Command12 } from "commander";
11
11
 
12
12
  // src/commands/login.ts
13
13
  import { Command } from "commander";
@@ -1756,9 +1756,223 @@ var deleteCommand2 = new Command9("delete").description("Delete a task (requires
1756
1756
  });
1757
1757
  var taskCommand = new Command9("task").description("Task operations (requires User OAuth with task scope)").addCommand(listCommand4).addCommand(createCommand2).addCommand(updateCommand2).addCommand(deleteCommand2);
1758
1758
 
1759
- // src/commands/mcp-cmd.ts
1759
+ // src/commands/board.ts
1760
1760
  import { Command as Command10 } from "commander";
1761
1761
 
1762
+ // src/api/board.ts
1763
+ var BASE_URL6 = "https://www.worksapis.com/v1.0";
1764
+ async function authedFetch5(url2, init, profile) {
1765
+ const token = await getValidUserToken(profile);
1766
+ const headers = new Headers(init.headers);
1767
+ headers.set("Authorization", `Bearer ${token}`);
1768
+ return fetch(url2, { ...init, headers });
1769
+ }
1770
+ async function handleError5(res) {
1771
+ if (res.status === 401) {
1772
+ throw new AuthError("User token expired. Run `nworks login --user --scope board` again.");
1773
+ }
1774
+ let code = "UNKNOWN";
1775
+ let description = `HTTP ${res.status}`;
1776
+ try {
1777
+ const body = await res.json();
1778
+ code = body.code ?? code;
1779
+ description = body.description ?? description;
1780
+ } catch {
1781
+ }
1782
+ throw new ApiError(code, description, res.status);
1783
+ }
1784
+ function safeParseJson(text) {
1785
+ const safe = text.replace(
1786
+ /"((?:board|post|domain|user)Id)"\s*:\s*(\d{16,})/g,
1787
+ '"$1":"$2"'
1788
+ );
1789
+ return JSON.parse(safe);
1790
+ }
1791
+ async function listBoards(count = 20, cursor, profile = "default") {
1792
+ const params = new URLSearchParams();
1793
+ params.set("count", String(count));
1794
+ if (cursor) params.set("cursor", cursor);
1795
+ const url2 = `${BASE_URL6}/boards?${params.toString()}`;
1796
+ if (process.env["NWORKS_VERBOSE"] === "1") {
1797
+ console.error(`[nworks] GET ${url2}`);
1798
+ }
1799
+ const res = await authedFetch5(url2, { method: "GET" }, profile);
1800
+ if (!res.ok) return handleError5(res);
1801
+ const text = await res.text();
1802
+ if (process.env["NWORKS_VERBOSE"] === "1") {
1803
+ console.error(`[nworks] Response: ${text}`);
1804
+ }
1805
+ const data = safeParseJson(text);
1806
+ return { boards: data.boards ?? [], responseMetaData: data.responseMetaData };
1807
+ }
1808
+ async function listPosts(boardId, count = 20, cursor, profile = "default") {
1809
+ const params = new URLSearchParams();
1810
+ params.set("count", String(count));
1811
+ if (cursor) params.set("cursor", cursor);
1812
+ const url2 = `${BASE_URL6}/boards/${boardId}/posts?${params.toString()}`;
1813
+ if (process.env["NWORKS_VERBOSE"] === "1") {
1814
+ console.error(`[nworks] GET ${url2}`);
1815
+ }
1816
+ const res = await authedFetch5(url2, { method: "GET" }, profile);
1817
+ if (!res.ok) return handleError5(res);
1818
+ const text = await res.text();
1819
+ if (process.env["NWORKS_VERBOSE"] === "1") {
1820
+ console.error(`[nworks] Response: ${text}`);
1821
+ }
1822
+ const data = safeParseJson(text);
1823
+ return { posts: data.posts ?? [], responseMetaData: data.responseMetaData };
1824
+ }
1825
+ async function readPost(boardId, postId, profile = "default") {
1826
+ const url2 = `${BASE_URL6}/boards/${boardId}/posts/${postId}`;
1827
+ if (process.env["NWORKS_VERBOSE"] === "1") {
1828
+ console.error(`[nworks] GET ${url2}`);
1829
+ }
1830
+ const res = await authedFetch5(url2, { method: "GET" }, profile);
1831
+ if (!res.ok) return handleError5(res);
1832
+ const text = await res.text();
1833
+ if (process.env["NWORKS_VERBOSE"] === "1") {
1834
+ console.error(`[nworks] Response: ${text}`);
1835
+ }
1836
+ return safeParseJson(text);
1837
+ }
1838
+ async function createPost(opts) {
1839
+ const profile = opts.profile ?? "default";
1840
+ const body = {
1841
+ title: opts.title,
1842
+ body: opts.body ?? ""
1843
+ };
1844
+ if (opts.enableComment !== void 0) body.enableComment = opts.enableComment;
1845
+ if (opts.sendNotifications !== void 0) body.sendNotifications = opts.sendNotifications;
1846
+ const url2 = `${BASE_URL6}/boards/${opts.boardId}/posts`;
1847
+ if (process.env["NWORKS_VERBOSE"] === "1") {
1848
+ console.error(`[nworks] POST ${url2}`);
1849
+ console.error(`[nworks] Body: ${JSON.stringify(body, null, 2)}`);
1850
+ }
1851
+ const res = await authedFetch5(
1852
+ url2,
1853
+ {
1854
+ method: "POST",
1855
+ headers: { "Content-Type": "application/json" },
1856
+ body: JSON.stringify(body)
1857
+ },
1858
+ profile
1859
+ );
1860
+ if (res.status === 201 || res.ok) {
1861
+ const text = await res.text();
1862
+ if (process.env["NWORKS_VERBOSE"] === "1") {
1863
+ console.error(`[nworks] Response: ${text}`);
1864
+ }
1865
+ return safeParseJson(text);
1866
+ }
1867
+ return handleError5(res);
1868
+ }
1869
+
1870
+ // src/commands/board.ts
1871
+ var listCommand5 = new Command10("list").description("List boards (requires User OAuth with board or board.read scope)").option("--count <n>", "Items per page (default: 20)", "20").option("--cursor <cursor>", "Pagination cursor").option("--profile <name>", "Profile name", "default").option("--json", "JSON output").action(async (opts) => {
1872
+ try {
1873
+ const result = await listBoards(
1874
+ parseInt(opts.count, 10),
1875
+ opts.cursor,
1876
+ opts.profile
1877
+ );
1878
+ const boards = result.boards.map((b) => ({
1879
+ boardId: b.boardId,
1880
+ boardName: b.boardName,
1881
+ description: b.description ?? ""
1882
+ }));
1883
+ output(
1884
+ { boards, count: boards.length, nextCursor: result.responseMetaData?.nextCursor ?? null },
1885
+ opts
1886
+ );
1887
+ } catch (err) {
1888
+ const error48 = err;
1889
+ errorOutput({ code: error48.code, message: error48.message }, opts);
1890
+ process.exitCode = 1;
1891
+ }
1892
+ });
1893
+ var postsCommand = new Command10("posts").description("List posts in a board (requires User OAuth with board or board.read scope)").requiredOption("--board <boardId>", "Board ID").option("--count <n>", "Items per page (default: 20)", "20").option("--cursor <cursor>", "Pagination cursor").option("--profile <name>", "Profile name", "default").option("--json", "JSON output").action(async (opts) => {
1894
+ try {
1895
+ const result = await listPosts(
1896
+ opts.board,
1897
+ parseInt(opts.count, 10),
1898
+ opts.cursor,
1899
+ opts.profile
1900
+ );
1901
+ const posts = result.posts.map((p) => ({
1902
+ postId: p.postId,
1903
+ title: p.title,
1904
+ userName: p.userName ?? "",
1905
+ readCount: p.readCount ?? 0,
1906
+ commentCount: p.commentCount ?? 0,
1907
+ createdTime: p.createdTime ?? ""
1908
+ }));
1909
+ output(
1910
+ { posts, count: posts.length, nextCursor: result.responseMetaData?.nextCursor ?? null },
1911
+ opts
1912
+ );
1913
+ } catch (err) {
1914
+ const error48 = err;
1915
+ errorOutput({ code: error48.code, message: error48.message }, opts);
1916
+ process.exitCode = 1;
1917
+ }
1918
+ });
1919
+ var readCommand2 = new Command10("read").description("Read a post detail (requires User OAuth with board or board.read scope)").requiredOption("--board <boardId>", "Board ID").requiredOption("--post <postId>", "Post ID").option("--profile <name>", "Profile name", "default").option("--json", "JSON output").action(async (opts) => {
1920
+ try {
1921
+ const post = await readPost(
1922
+ opts.board,
1923
+ opts.post,
1924
+ opts.profile
1925
+ );
1926
+ output(
1927
+ {
1928
+ postId: post.postId,
1929
+ boardId: post.boardId,
1930
+ title: post.title,
1931
+ body: post.body ?? "",
1932
+ userName: post.userName ?? "",
1933
+ readCount: post.readCount ?? 0,
1934
+ commentCount: post.commentCount ?? 0,
1935
+ createdTime: post.createdTime ?? "",
1936
+ updatedTime: post.updatedTime ?? ""
1937
+ },
1938
+ opts
1939
+ );
1940
+ } catch (err) {
1941
+ const error48 = err;
1942
+ errorOutput({ code: error48.code, message: error48.message }, opts);
1943
+ process.exitCode = 1;
1944
+ }
1945
+ });
1946
+ var createCommand3 = new Command10("create").description("Create a post in a board (requires User OAuth with board scope)").requiredOption("--board <boardId>", "Board ID").requiredOption("--title <title>", "Post title").option("--body <text>", "Post body").option("--no-comment", "Disable comments").option("--notify", "Send notification").option("--profile <name>", "Profile name", "default").option("--json", "JSON output").action(async (opts) => {
1947
+ try {
1948
+ const post = await createPost({
1949
+ boardId: opts.board,
1950
+ title: opts.title,
1951
+ body: opts.body,
1952
+ enableComment: opts.comment,
1953
+ sendNotifications: opts.notify ?? false,
1954
+ profile: opts.profile
1955
+ });
1956
+ output(
1957
+ {
1958
+ success: true,
1959
+ postId: post.postId,
1960
+ boardId: post.boardId,
1961
+ title: post.title
1962
+ },
1963
+ opts
1964
+ );
1965
+ } catch (err) {
1966
+ const error48 = err;
1967
+ errorOutput({ code: error48.code, message: error48.message }, opts);
1968
+ process.exitCode = 1;
1969
+ }
1970
+ });
1971
+ var boardCommand = new Command10("board").description("Board operations (requires User OAuth)").addCommand(listCommand5).addCommand(postsCommand).addCommand(readCommand2).addCommand(createCommand3);
1972
+
1973
+ // src/commands/mcp-cmd.ts
1974
+ import { Command as Command11 } from "commander";
1975
+
1762
1976
  // src/mcp/server.ts
1763
1977
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1764
1978
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -15623,6 +15837,7 @@ function registerTools(server) {
15623
15837
  );
15624
15838
  const events = result.events.flatMap(
15625
15839
  (e) => e.eventComponents.map((c) => ({
15840
+ eventId: c.eventId,
15626
15841
  summary: c.summary,
15627
15842
  start: c.start.dateTime ?? c.start.date ?? "",
15628
15843
  end: c.end.dateTime ?? c.end.date ?? "",
@@ -16135,6 +16350,127 @@ function registerTools(server) {
16135
16350
  }
16136
16351
  }
16137
16352
  );
16353
+ server.tool(
16354
+ "nworks_board_list",
16355
+ "\uAC8C\uC2DC\uD310 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4 (User OAuth board \uB610\uB294 board.read scope \uD544\uC694)",
16356
+ {
16357
+ count: external_exports.number().optional().describe("\uD398\uC774\uC9C0\uB2F9 \uD56D\uBAA9 \uC218 (\uAE30\uBCF8: 20)"),
16358
+ cursor: external_exports.string().optional().describe("\uD398\uC774\uC9C0\uB124\uC774\uC158 \uCEE4\uC11C")
16359
+ },
16360
+ async ({ count, cursor }) => {
16361
+ try {
16362
+ const result = await listBoards(count ?? 20, cursor);
16363
+ const boards = result.boards.map((b) => ({
16364
+ boardId: b.boardId,
16365
+ boardName: b.boardName,
16366
+ description: b.description ?? ""
16367
+ }));
16368
+ return {
16369
+ content: [{ type: "text", text: JSON.stringify({ boards, count: boards.length, nextCursor: result.responseMetaData?.nextCursor ?? null }) }]
16370
+ };
16371
+ } catch (err) {
16372
+ const error48 = err;
16373
+ return {
16374
+ content: [{ type: "text", text: `Error: ${error48.message}` }],
16375
+ isError: true
16376
+ };
16377
+ }
16378
+ }
16379
+ );
16380
+ server.tool(
16381
+ "nworks_board_posts",
16382
+ "\uAC8C\uC2DC\uD310\uC758 \uAE00 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4 (User OAuth board \uB610\uB294 board.read scope \uD544\uC694)",
16383
+ {
16384
+ boardId: external_exports.string().describe("\uAC8C\uC2DC\uD310 ID"),
16385
+ count: external_exports.number().optional().describe("\uD398\uC774\uC9C0\uB2F9 \uD56D\uBAA9 \uC218 (\uAE30\uBCF8: 20, \uCD5C\uB300: 40)"),
16386
+ cursor: external_exports.string().optional().describe("\uD398\uC774\uC9C0\uB124\uC774\uC158 \uCEE4\uC11C")
16387
+ },
16388
+ async ({ boardId, count, cursor }) => {
16389
+ try {
16390
+ const result = await listPosts(boardId, count ?? 20, cursor);
16391
+ const posts = result.posts.map((p) => ({
16392
+ postId: p.postId,
16393
+ title: p.title,
16394
+ userName: p.userName ?? "",
16395
+ readCount: p.readCount ?? 0,
16396
+ commentCount: p.commentCount ?? 0,
16397
+ createdTime: p.createdTime ?? ""
16398
+ }));
16399
+ return {
16400
+ content: [{ type: "text", text: JSON.stringify({ posts, count: posts.length, nextCursor: result.responseMetaData?.nextCursor ?? null }) }]
16401
+ };
16402
+ } catch (err) {
16403
+ const error48 = err;
16404
+ return {
16405
+ content: [{ type: "text", text: `Error: ${error48.message}` }],
16406
+ isError: true
16407
+ };
16408
+ }
16409
+ }
16410
+ );
16411
+ server.tool(
16412
+ "nworks_board_read",
16413
+ "\uAC8C\uC2DC\uD310 \uAE00\uC758 \uC0C1\uC138 \uB0B4\uC6A9\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4 (User OAuth board \uB610\uB294 board.read scope \uD544\uC694)",
16414
+ {
16415
+ boardId: external_exports.string().describe("\uAC8C\uC2DC\uD310 ID"),
16416
+ postId: external_exports.string().describe("\uAE00 ID")
16417
+ },
16418
+ async ({ boardId, postId }) => {
16419
+ try {
16420
+ const post = await readPost(boardId, postId);
16421
+ return {
16422
+ content: [{ type: "text", text: JSON.stringify({
16423
+ postId: post.postId,
16424
+ boardId: post.boardId,
16425
+ title: post.title,
16426
+ body: post.body ?? "",
16427
+ userName: post.userName ?? "",
16428
+ readCount: post.readCount ?? 0,
16429
+ commentCount: post.commentCount ?? 0,
16430
+ createdTime: post.createdTime ?? "",
16431
+ updatedTime: post.updatedTime ?? ""
16432
+ }) }]
16433
+ };
16434
+ } catch (err) {
16435
+ const error48 = err;
16436
+ return {
16437
+ content: [{ type: "text", text: `Error: ${error48.message}` }],
16438
+ isError: true
16439
+ };
16440
+ }
16441
+ }
16442
+ );
16443
+ server.tool(
16444
+ "nworks_board_create",
16445
+ "\uAC8C\uC2DC\uD310\uC5D0 \uAE00\uC744 \uC791\uC131\uD569\uB2C8\uB2E4 (User OAuth board scope \uD544\uC694)",
16446
+ {
16447
+ boardId: external_exports.string().describe("\uAC8C\uC2DC\uD310 ID"),
16448
+ title: external_exports.string().describe("\uAE00 \uC81C\uBAA9"),
16449
+ body: external_exports.string().optional().describe("\uAE00 \uBCF8\uBB38"),
16450
+ enableComment: external_exports.boolean().optional().describe("\uB313\uAE00 \uD5C8\uC6A9 (\uAE30\uBCF8: true)"),
16451
+ sendNotifications: external_exports.boolean().optional().describe("\uC54C\uB9BC \uBC1C\uC1A1 (\uAE30\uBCF8: false)")
16452
+ },
16453
+ async ({ boardId, title, body, enableComment, sendNotifications }) => {
16454
+ try {
16455
+ const post = await createPost({
16456
+ boardId,
16457
+ title,
16458
+ body,
16459
+ enableComment,
16460
+ sendNotifications
16461
+ });
16462
+ return {
16463
+ content: [{ type: "text", text: JSON.stringify({ success: true, postId: post.postId, boardId: post.boardId, title: post.title }) }]
16464
+ };
16465
+ } catch (err) {
16466
+ const error48 = err;
16467
+ return {
16468
+ content: [{ type: "text", text: `Error: ${error48.message}` }],
16469
+ isError: true
16470
+ };
16471
+ }
16472
+ }
16473
+ );
16138
16474
  server.tool(
16139
16475
  "nworks_whoami",
16140
16476
  "\uD604\uC7AC \uC778\uC99D\uB41C NAVER WORKS \uACC4\uC815 \uC815\uBCF4\uB97C \uD655\uC778\uD569\uB2C8\uB2E4",
@@ -16176,7 +16512,7 @@ async function startMcpServer() {
16176
16512
  }
16177
16513
 
16178
16514
  // src/commands/mcp-cmd.ts
16179
- var mcpCommand = new Command10("mcp").description("Start MCP server (stdio transport)").option("--list-tools", "List available MCP tools").action(async (opts) => {
16515
+ var mcpCommand = new Command11("mcp").description("Start MCP server (stdio transport)").option("--list-tools", "List available MCP tools").action(async (opts) => {
16180
16516
  if (opts.listTools) {
16181
16517
  console.log("nworks_message_send \u2014 Send message to user or channel");
16182
16518
  console.log("nworks_message_members \u2014 List channel members");
@@ -16191,7 +16527,7 @@ var mcpCommand = new Command10("mcp").description("Start MCP server (stdio trans
16191
16527
  // src/index.ts
16192
16528
  var require2 = createRequire(import.meta.url);
16193
16529
  var { version: version2 } = require2("../package.json");
16194
- var program = new Command11().name("nworks").description("NAVER WORKS CLI \u2014 built for humans and AI agents").version(version2).option("--json", "Always output JSON").option("-v, --verbose", "Debug logging").option("--dry-run", "Print request without calling API").option("-p, --profile <name>", "Profile name", "default");
16530
+ var program = new Command12().name("nworks").description("NAVER WORKS CLI \u2014 built for humans and AI agents").version(version2).option("--json", "Always output JSON").option("-v, --verbose", "Debug logging").option("--dry-run", "Print request without calling API").option("-p, --profile <name>", "Profile name", "default");
16195
16531
  program.addCommand(loginCommand);
16196
16532
  program.addCommand(logoutCommand);
16197
16533
  program.addCommand(whoamiCommand);
@@ -16201,6 +16537,7 @@ program.addCommand(calendarCommand);
16201
16537
  program.addCommand(driveCommand);
16202
16538
  program.addCommand(mailCommand);
16203
16539
  program.addCommand(taskCommand);
16540
+ program.addCommand(boardCommand);
16204
16541
  program.addCommand(mcpCommand);
16205
16542
  program.parse();
16206
16543
  //# sourceMappingURL=index.js.map