nworks 0.1.0 → 0.2.1

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/dist/mcp.js CHANGED
@@ -13802,6 +13802,7 @@ import { join } from "path";
13802
13802
  var CONFIG_DIR = join(homedir(), ".config", "nworks");
13803
13803
  var CREDENTIALS_PATH = join(CONFIG_DIR, "credentials.json");
13804
13804
  var TOKEN_PATH = join(CONFIG_DIR, "token.json");
13805
+ var USER_TOKEN_PATH = join(CONFIG_DIR, "user-token.json");
13805
13806
  async function ensureConfigDir() {
13806
13807
  if (!existsSync(CONFIG_DIR)) {
13807
13808
  await mkdir(CONFIG_DIR, { recursive: true });
@@ -13862,6 +13863,29 @@ async function saveToken(token, profile = "default") {
13862
13863
  tokens[profile] = token;
13863
13864
  await writeFile(TOKEN_PATH, JSON.stringify(tokens, null, 2), "utf-8");
13864
13865
  }
13866
+ async function loadUserToken(profile = "default") {
13867
+ if (!existsSync(USER_TOKEN_PATH)) return null;
13868
+ const raw = await readFile(USER_TOKEN_PATH, "utf-8");
13869
+ const tokens = JSON.parse(raw);
13870
+ const entry = tokens[profile];
13871
+ if (!entry) return null;
13872
+ return {
13873
+ accessToken: String(entry["accessToken"]),
13874
+ refreshToken: String(entry["refreshToken"]),
13875
+ expiresAt: Number(entry["expiresAt"]),
13876
+ scope: String(entry["scope"] ?? "")
13877
+ };
13878
+ }
13879
+ async function saveUserToken(token, profile = "default") {
13880
+ await ensureConfigDir();
13881
+ let tokens = {};
13882
+ if (existsSync(USER_TOKEN_PATH)) {
13883
+ const raw = await readFile(USER_TOKEN_PATH, "utf-8");
13884
+ tokens = JSON.parse(raw);
13885
+ }
13886
+ tokens[profile] = token;
13887
+ await writeFile(USER_TOKEN_PATH, JSON.stringify(tokens, null, 2), "utf-8");
13888
+ }
13865
13889
 
13866
13890
  // src/auth/jwt.ts
13867
13891
  import { readFile as readFile2 } from "fs/promises";
@@ -14036,6 +14060,89 @@ async function listUsers(profile = "default") {
14036
14060
  return { users: result.users ?? [], responseMetaData: result.responseMetaData };
14037
14061
  }
14038
14062
 
14063
+ // src/auth/oauth-user.ts
14064
+ import { createServer } from "http";
14065
+ import { URL as URL2 } from "url";
14066
+ var TOKEN_URL = "https://auth.worksmobile.com/oauth2/v2.0/token";
14067
+ var REDIRECT_PORT = 9876;
14068
+ var REDIRECT_URI = `http://localhost:${REDIRECT_PORT}/callback`;
14069
+ async function refreshUserToken(refreshToken2, profile = "default") {
14070
+ const creds = await loadCredentials(profile);
14071
+ const body = new URLSearchParams({
14072
+ grant_type: "refresh_token",
14073
+ refresh_token: refreshToken2,
14074
+ client_id: creds.clientId,
14075
+ client_secret: creds.clientSecret
14076
+ });
14077
+ const res = await fetch(TOKEN_URL, {
14078
+ method: "POST",
14079
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
14080
+ body: body.toString()
14081
+ });
14082
+ if (!res.ok) {
14083
+ const text = await res.text();
14084
+ throw new AuthError(`Token refresh failed (${res.status}): ${text}`);
14085
+ }
14086
+ const data = await res.json();
14087
+ return {
14088
+ accessToken: data.access_token,
14089
+ refreshToken: data.refresh_token ?? refreshToken2,
14090
+ expiresAt: Math.floor(Date.now() / 1e3) + Number(data.expires_in),
14091
+ scope: data.scope
14092
+ };
14093
+ }
14094
+
14095
+ // src/auth/token-user.ts
14096
+ async function getValidUserToken(profile = "default") {
14097
+ const cached2 = await loadUserToken(profile);
14098
+ if (!cached2) {
14099
+ throw new AuthError(
14100
+ "User OAuth token not found. Run `nworks login --user` first."
14101
+ );
14102
+ }
14103
+ if (cached2.expiresAt > Date.now() / 1e3 + 300) {
14104
+ return cached2.accessToken;
14105
+ }
14106
+ const refreshed = await refreshUserToken(cached2.refreshToken, profile);
14107
+ await saveUserToken(refreshed, profile);
14108
+ return refreshed.accessToken;
14109
+ }
14110
+
14111
+ // src/api/calendar.ts
14112
+ var BASE_URL2 = "https://www.worksapis.com/v1.0";
14113
+ async function listEvents(fromDateTime, untilDateTime, userId = "me", profile = "default") {
14114
+ const token = await getValidUserToken(profile);
14115
+ const from = encodeURIComponent(fromDateTime);
14116
+ const until = encodeURIComponent(untilDateTime);
14117
+ const url2 = `${BASE_URL2}/users/${userId}/calendar/events?fromDateTime=${from}&untilDateTime=${until}`;
14118
+ if (process.env["NWORKS_VERBOSE"] === "1") {
14119
+ console.error(`[nworks] GET ${url2}`);
14120
+ }
14121
+ const res = await fetch(url2, {
14122
+ method: "GET",
14123
+ headers: {
14124
+ Authorization: `Bearer ${token}`,
14125
+ "Content-Type": "application/json"
14126
+ }
14127
+ });
14128
+ if (res.status === 401) {
14129
+ throw new AuthError("User token expired. Run `nworks login --user` again.");
14130
+ }
14131
+ if (!res.ok) {
14132
+ let code = "UNKNOWN";
14133
+ let description = `HTTP ${res.status}`;
14134
+ try {
14135
+ const errorBody = await res.json();
14136
+ code = errorBody.code ?? code;
14137
+ description = errorBody.description ?? description;
14138
+ } catch {
14139
+ }
14140
+ throw new ApiError(code, description, res.status);
14141
+ }
14142
+ const data = await res.json();
14143
+ return { events: data.events ?? [] };
14144
+ }
14145
+
14039
14146
  // src/mcp/tools.ts
14040
14147
  function registerTools(server) {
14041
14148
  server.tool(
@@ -14111,6 +14218,41 @@ function registerTools(server) {
14111
14218
  }
14112
14219
  }
14113
14220
  );
14221
+ server.tool(
14222
+ "nworks_calendar_list",
14223
+ "\uC0AC\uC6A9\uC790\uC758 \uCE98\uB9B0\uB354 \uC77C\uC815\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4 (User OAuth calendar.read scope \uD544\uC694. \uBA3C\uC800 nworks login --user \uD544\uC694)",
14224
+ {
14225
+ fromDateTime: external_exports.string().describe("\uC2DC\uC791 \uC77C\uC2DC (YYYY-MM-DDThh:mm:ss+09:00)"),
14226
+ untilDateTime: external_exports.string().describe("\uC885\uB8CC \uC77C\uC2DC (YYYY-MM-DDThh:mm:ss+09:00)"),
14227
+ userId: external_exports.string().optional().describe("\uB300\uC0C1 \uC0AC\uC6A9\uC790 ID (\uBBF8\uC9C0\uC815 \uC2DC me)")
14228
+ },
14229
+ async ({ fromDateTime, untilDateTime, userId }) => {
14230
+ try {
14231
+ const result = await listEvents(
14232
+ fromDateTime,
14233
+ untilDateTime,
14234
+ userId ?? "me"
14235
+ );
14236
+ const events = result.events.flatMap(
14237
+ (e) => e.eventComponents.map((c) => ({
14238
+ summary: c.summary,
14239
+ start: c.start.dateTime ?? c.start.date ?? "",
14240
+ end: c.end.dateTime ?? c.end.date ?? "",
14241
+ location: c.location ?? ""
14242
+ }))
14243
+ );
14244
+ return {
14245
+ content: [{ type: "text", text: JSON.stringify({ events, count: events.length }) }]
14246
+ };
14247
+ } catch (err) {
14248
+ const error48 = err;
14249
+ return {
14250
+ content: [{ type: "text", text: `Error: ${error48.message}` }],
14251
+ isError: true
14252
+ };
14253
+ }
14254
+ }
14255
+ );
14114
14256
  server.tool(
14115
14257
  "nworks_whoami",
14116
14258
  "\uD604\uC7AC \uC778\uC99D\uB41C NAVER WORKS \uACC4\uC815 \uC815\uBCF4\uB97C \uD655\uC778\uD569\uB2C8\uB2E4",
@@ -14144,7 +14286,7 @@ function registerTools(server) {
14144
14286
  async function startMcpServer() {
14145
14287
  const server = new McpServer({
14146
14288
  name: "nworks",
14147
- version: "0.1.0"
14289
+ version: "0.2.0"
14148
14290
  });
14149
14291
  registerTools(server);
14150
14292
  const transport = new StdioServerTransport();