@davidwoods/skylight-mcp 0.0.0-stage → 1.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.
Files changed (168) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +583 -2
  3. package/THIRD_PARTY_NOTICES.md +38 -0
  4. package/dist/api/auth.d.ts +34 -0
  5. package/dist/api/auth.d.ts.map +1 -0
  6. package/dist/api/auth.js +328 -0
  7. package/dist/api/auth.js.map +1 -0
  8. package/dist/api/client.d.ts +103 -0
  9. package/dist/api/client.d.ts.map +1 -0
  10. package/dist/api/client.js +324 -0
  11. package/dist/api/client.js.map +1 -0
  12. package/dist/api/constants.d.ts +4 -0
  13. package/dist/api/constants.d.ts.map +1 -0
  14. package/dist/api/constants.js +4 -0
  15. package/dist/api/constants.js.map +1 -0
  16. package/dist/api/credential-store.d.ts +38 -0
  17. package/dist/api/credential-store.d.ts.map +1 -0
  18. package/dist/api/credential-store.js +273 -0
  19. package/dist/api/credential-store.js.map +1 -0
  20. package/dist/api/credential-types.d.ts +15 -0
  21. package/dist/api/credential-types.d.ts.map +1 -0
  22. package/dist/api/credential-types.js +11 -0
  23. package/dist/api/credential-types.js.map +1 -0
  24. package/dist/api/endpoints/calendar.d.ts +36 -0
  25. package/dist/api/endpoints/calendar.d.ts.map +1 -0
  26. package/dist/api/endpoints/calendar.js +74 -0
  27. package/dist/api/endpoints/calendar.js.map +1 -0
  28. package/dist/api/endpoints/categories.d.ts +32 -0
  29. package/dist/api/endpoints/categories.d.ts.map +1 -0
  30. package/dist/api/endpoints/categories.js +77 -0
  31. package/dist/api/endpoints/categories.js.map +1 -0
  32. package/dist/api/endpoints/chores.d.ts +50 -0
  33. package/dist/api/endpoints/chores.d.ts.map +1 -0
  34. package/dist/api/endpoints/chores.js +106 -0
  35. package/dist/api/endpoints/chores.js.map +1 -0
  36. package/dist/api/endpoints/devices.d.ts +6 -0
  37. package/dist/api/endpoints/devices.d.ts.map +1 -0
  38. package/dist/api/endpoints/devices.js +10 -0
  39. package/dist/api/endpoints/devices.js.map +1 -0
  40. package/dist/api/endpoints/frames.d.ts +6 -0
  41. package/dist/api/endpoints/frames.d.ts.map +1 -0
  42. package/dist/api/endpoints/frames.js +10 -0
  43. package/dist/api/endpoints/frames.js.map +1 -0
  44. package/dist/api/endpoints/lists.d.ts +55 -0
  45. package/dist/api/endpoints/lists.d.ts.map +1 -0
  46. package/dist/api/endpoints/lists.js +125 -0
  47. package/dist/api/endpoints/lists.js.map +1 -0
  48. package/dist/api/endpoints/meals.d.ts +154 -0
  49. package/dist/api/endpoints/meals.d.ts.map +1 -0
  50. package/dist/api/endpoints/meals.js +194 -0
  51. package/dist/api/endpoints/meals.js.map +1 -0
  52. package/dist/api/endpoints/messages.d.ts +98 -0
  53. package/dist/api/endpoints/messages.d.ts.map +1 -0
  54. package/dist/api/endpoints/messages.js +116 -0
  55. package/dist/api/endpoints/messages.js.map +1 -0
  56. package/dist/api/endpoints/misc.d.ts +27 -0
  57. package/dist/api/endpoints/misc.d.ts.map +1 -0
  58. package/dist/api/endpoints/misc.js +19 -0
  59. package/dist/api/endpoints/misc.js.map +1 -0
  60. package/dist/api/endpoints/photos.d.ts +13 -0
  61. package/dist/api/endpoints/photos.d.ts.map +1 -0
  62. package/dist/api/endpoints/photos.js +10 -0
  63. package/dist/api/endpoints/photos.js.map +1 -0
  64. package/dist/api/endpoints/rewards.d.ts +49 -0
  65. package/dist/api/endpoints/rewards.d.ts.map +1 -0
  66. package/dist/api/endpoints/rewards.js +106 -0
  67. package/dist/api/endpoints/rewards.js.map +1 -0
  68. package/dist/api/endpoints/shape.d.ts +11 -0
  69. package/dist/api/endpoints/shape.d.ts.map +1 -0
  70. package/dist/api/endpoints/shape.js +34 -0
  71. package/dist/api/endpoints/shape.js.map +1 -0
  72. package/dist/api/endpoints/taskbox.d.ts +13 -0
  73. package/dist/api/endpoints/taskbox.d.ts.map +1 -0
  74. package/dist/api/endpoints/taskbox.js +22 -0
  75. package/dist/api/endpoints/taskbox.js.map +1 -0
  76. package/dist/api/oauth.d.ts +62 -0
  77. package/dist/api/oauth.d.ts.map +1 -0
  78. package/dist/api/oauth.js +206 -0
  79. package/dist/api/oauth.js.map +1 -0
  80. package/dist/api/types.d.ts +277 -0
  81. package/dist/api/types.d.ts.map +1 -0
  82. package/dist/api/types.js +5 -0
  83. package/dist/api/types.js.map +1 -0
  84. package/dist/cli.d.ts +23 -0
  85. package/dist/cli.d.ts.map +1 -0
  86. package/dist/cli.js +105 -0
  87. package/dist/cli.js.map +1 -0
  88. package/dist/config.d.ts +27 -0
  89. package/dist/config.d.ts.map +1 -0
  90. package/dist/config.js +78 -0
  91. package/dist/config.js.map +1 -0
  92. package/dist/context.d.ts +17 -0
  93. package/dist/context.d.ts.map +1 -0
  94. package/dist/context.js +17 -0
  95. package/dist/context.js.map +1 -0
  96. package/dist/doctor.d.ts +19 -0
  97. package/dist/doctor.d.ts.map +1 -0
  98. package/dist/doctor.js +109 -0
  99. package/dist/doctor.js.map +1 -0
  100. package/dist/index.d.ts +3 -0
  101. package/dist/index.d.ts.map +1 -0
  102. package/dist/index.js +24 -0
  103. package/dist/index.js.map +1 -0
  104. package/dist/server.d.ts +7 -0
  105. package/dist/server.d.ts.map +1 -0
  106. package/dist/server.js +29 -0
  107. package/dist/server.js.map +1 -0
  108. package/dist/tools/calendar.d.ts +3 -0
  109. package/dist/tools/calendar.d.ts.map +1 -0
  110. package/dist/tools/calendar.js +298 -0
  111. package/dist/tools/calendar.js.map +1 -0
  112. package/dist/tools/chores.d.ts +3 -0
  113. package/dist/tools/chores.d.ts.map +1 -0
  114. package/dist/tools/chores.js +349 -0
  115. package/dist/tools/chores.js.map +1 -0
  116. package/dist/tools/family.d.ts +3 -0
  117. package/dist/tools/family.d.ts.map +1 -0
  118. package/dist/tools/family.js +182 -0
  119. package/dist/tools/family.js.map +1 -0
  120. package/dist/tools/lists.d.ts +3 -0
  121. package/dist/tools/lists.d.ts.map +1 -0
  122. package/dist/tools/lists.js +481 -0
  123. package/dist/tools/lists.js.map +1 -0
  124. package/dist/tools/meals.d.ts +3 -0
  125. package/dist/tools/meals.d.ts.map +1 -0
  126. package/dist/tools/meals.js +491 -0
  127. package/dist/tools/meals.js.map +1 -0
  128. package/dist/tools/messages.d.ts +3 -0
  129. package/dist/tools/messages.d.ts.map +1 -0
  130. package/dist/tools/messages.js +195 -0
  131. package/dist/tools/messages.js.map +1 -0
  132. package/dist/tools/misc.d.ts +3 -0
  133. package/dist/tools/misc.d.ts.map +1 -0
  134. package/dist/tools/misc.js +102 -0
  135. package/dist/tools/misc.js.map +1 -0
  136. package/dist/tools/photos.d.ts +3 -0
  137. package/dist/tools/photos.d.ts.map +1 -0
  138. package/dist/tools/photos.js +42 -0
  139. package/dist/tools/photos.js.map +1 -0
  140. package/dist/tools/planning.d.ts +15 -0
  141. package/dist/tools/planning.d.ts.map +1 -0
  142. package/dist/tools/planning.js +303 -0
  143. package/dist/tools/planning.js.map +1 -0
  144. package/dist/tools/registry.d.ts +39 -0
  145. package/dist/tools/registry.d.ts.map +1 -0
  146. package/dist/tools/registry.js +142 -0
  147. package/dist/tools/registry.js.map +1 -0
  148. package/dist/tools/rewards.d.ts +3 -0
  149. package/dist/tools/rewards.d.ts.map +1 -0
  150. package/dist/tools/rewards.js +339 -0
  151. package/dist/tools/rewards.js.map +1 -0
  152. package/dist/tools/tasks.d.ts +3 -0
  153. package/dist/tools/tasks.d.ts.map +1 -0
  154. package/dist/tools/tasks.js +68 -0
  155. package/dist/tools/tasks.js.map +1 -0
  156. package/dist/utils/dates.d.ts +26 -0
  157. package/dist/utils/dates.d.ts.map +1 -0
  158. package/dist/utils/dates.js +109 -0
  159. package/dist/utils/dates.js.map +1 -0
  160. package/dist/utils/errors.d.ts +45 -0
  161. package/dist/utils/errors.d.ts.map +1 -0
  162. package/dist/utils/errors.js +102 -0
  163. package/dist/utils/errors.js.map +1 -0
  164. package/dist/utils/schedule.d.ts +86 -0
  165. package/dist/utils/schedule.d.ts.map +1 -0
  166. package/dist/utils/schedule.js +189 -0
  167. package/dist/utils/schedule.js.map +1 -0
  168. package/package.json +76 -4
@@ -0,0 +1,324 @@
1
+ import { getConfig, usesEmailAuth } from "../config.js";
2
+ import { detectSubscriptionStatus, login } from "./auth.js";
3
+ import { SKYLIGHT_API_VERSION, SKYLIGHT_BASE_URL } from "./constants.js";
4
+ import { CredentialStoreError } from "./credential-types.js";
5
+ import { currentContext } from "../context.js";
6
+ import { OAuthError, isExpired, refreshCredential } from "./oauth.js";
7
+ import { AuthenticationError, NotFoundError, RateLimitError, SkylightError, } from "../utils/errors.js";
8
+ /**
9
+ * Skylight API Client
10
+ * Handles authentication and HTTP requests to the Skylight API
11
+ */
12
+ export class SkylightClient {
13
+ config;
14
+ resolvedToken = null;
15
+ loginPromise = null;
16
+ subscriptionStatus = null;
17
+ store;
18
+ stored = null;
19
+ storedLoaded = false;
20
+ refreshPromise = null;
21
+ /**
22
+ * @param store Where the OAuth credential lives. `null` means "no stored
23
+ * credential is available" (hosted mode, where the token arrives in config).
24
+ */
25
+ constructor(config, store = null) {
26
+ this.config = config ?? getConfig();
27
+ this.store = store;
28
+ }
29
+ getStore() {
30
+ if (!this.store) {
31
+ throw new AuthenticationError("No credential store is configured for this server.");
32
+ }
33
+ return this.store;
34
+ }
35
+ /** Load the stored OAuth credential once. Fails closed if the store is unusable. */
36
+ async loadStored() {
37
+ if (this.storedLoaded)
38
+ return this.stored;
39
+ try {
40
+ this.stored = await this.getStore().read();
41
+ }
42
+ catch (error) {
43
+ if (error instanceof CredentialStoreError)
44
+ throw new AuthenticationError(error.message);
45
+ throw error;
46
+ }
47
+ this.storedLoaded = true;
48
+ if (this.stored) {
49
+ this.subscriptionStatus = (await detectSubscriptionStatus(this.stored.accessToken));
50
+ }
51
+ return this.stored;
52
+ }
53
+ /**
54
+ * Refresh tokens rotate on use, so concurrent refreshes would invalidate each
55
+ * other. Share one in-flight promise, and re-read the store first in case
56
+ * another process already rotated the credential.
57
+ */
58
+ refreshStored() {
59
+ if (!this.refreshPromise) {
60
+ const current = this.stored;
61
+ if (!current)
62
+ throw new AuthenticationError("Not signed in. Run `skylight-mcp auth login`.");
63
+ this.refreshPromise = (async () => {
64
+ const latest = (await this.getStore().read().catch(() => null)) ?? current;
65
+ if (!isExpired(latest) && latest.accessToken !== current.accessToken) {
66
+ this.stored = latest;
67
+ return latest;
68
+ }
69
+ let next;
70
+ try {
71
+ next = await refreshCredential(latest);
72
+ }
73
+ catch (error) {
74
+ if (error instanceof OAuthError) {
75
+ throw new AuthenticationError(`${error.message} Run \`skylight-mcp auth login\` again.`);
76
+ }
77
+ throw error;
78
+ }
79
+ this.stored = next;
80
+ try {
81
+ await this.getStore().write(next);
82
+ }
83
+ catch (error) {
84
+ // The rotated refresh token is only in memory now; this session keeps working.
85
+ console.error(`[client] Could not save refreshed credential: ${error.message}`);
86
+ }
87
+ return next;
88
+ })().finally(() => {
89
+ this.refreshPromise = null;
90
+ });
91
+ }
92
+ return this.refreshPromise;
93
+ }
94
+ /**
95
+ * Get the authentication credentials
96
+ * If using email/password auth, will login first
97
+ */
98
+ async getCredentials() {
99
+ // 1. Explicit token from the environment
100
+ if (this.config.token) {
101
+ return { token: this.config.token };
102
+ }
103
+ // 2. Stored OAuth credential (refreshed when expired)
104
+ const stored = this.store ? await this.loadStored() : null;
105
+ if (stored) {
106
+ const fresh = isExpired(this.stored ?? stored) ? await this.refreshStored() : (this.stored ?? stored);
107
+ return { token: fresh.accessToken };
108
+ }
109
+ // 3. Deprecated email/password login
110
+ if (!usesEmailAuth(this.config)) {
111
+ throw new AuthenticationError("Not signed in to Skylight. Run `skylight-mcp auth login`, then restart your MCP client.");
112
+ }
113
+ if (this.resolvedToken) {
114
+ return { token: this.resolvedToken };
115
+ }
116
+ // If already logging in, wait for that to complete
117
+ if (this.loginPromise) {
118
+ return this.loginPromise;
119
+ }
120
+ // Login with email/password
121
+ this.loginPromise = this.performLogin();
122
+ try {
123
+ const result = await this.loginPromise;
124
+ this.resolvedToken = result.token;
125
+ return result;
126
+ }
127
+ finally {
128
+ this.loginPromise = null;
129
+ }
130
+ }
131
+ /**
132
+ * Perform login and return token and userId
133
+ */
134
+ async performLogin() {
135
+ const { email, password } = this.config;
136
+ if (!email || !password) {
137
+ throw new AuthenticationError("Email and password are required for login");
138
+ }
139
+ console.error("Logging in to Skylight...");
140
+ const result = await login(email, password);
141
+ this.subscriptionStatus = result.subscriptionStatus;
142
+ console.error(`Logged in as ${result.email}${result.subscriptionStatus ? ` (${result.subscriptionStatus})` : ""}`);
143
+ return { token: result.token };
144
+ }
145
+ /**
146
+ * Build the Authorization header
147
+ * Email/password auth now resolves to an OAuth bearer token.
148
+ * Manual token auth still respects the configured auth type.
149
+ */
150
+ async getAuthHeader() {
151
+ const { token } = await this.getCredentials();
152
+ // Only an explicit manual token may use Basic; everything else is a bearer token.
153
+ if (this.config.token && this.config.authType === "basic") {
154
+ return `Basic ${token}`;
155
+ }
156
+ return `Bearer ${token}`;
157
+ }
158
+ /**
159
+ * Build URL with query parameters
160
+ */
161
+ buildUrl(endpoint, params) {
162
+ const url = new URL(endpoint, SKYLIGHT_BASE_URL);
163
+ // Pin the host: an absolute or protocol-relative endpoint must never
164
+ // redirect the bearer token to another origin.
165
+ if (url.origin !== new URL(SKYLIGHT_BASE_URL).origin) {
166
+ throw new SkylightError("Refusing to send a request outside the Skylight API host.", "HOST_NOT_ALLOWED");
167
+ }
168
+ if (params) {
169
+ for (const [key, value] of Object.entries(params)) {
170
+ if (value !== undefined) {
171
+ url.searchParams.set(key, String(value));
172
+ }
173
+ }
174
+ }
175
+ return url.toString();
176
+ }
177
+ /**
178
+ * Handle API response errors
179
+ */
180
+ async handleResponseError(response, url) {
181
+ const status = response.status;
182
+ if (status === 401) {
183
+ // Clear cached credentials on auth failure
184
+ this.resolvedToken = null;
185
+ console.error(`[client] 401 Unauthorized for ${url}`);
186
+ if (!this.config.token) {
187
+ throw new AuthenticationError("API request returned 401. Your frame ID may be wrong or not belong to this account " +
188
+ "(check SKYLIGHT_FRAME_ID), or your session may have been revoked " +
189
+ "(run `skylight-mcp auth login` again).");
190
+ }
191
+ throw new AuthenticationError();
192
+ }
193
+ if (status === 404) {
194
+ throw new NotFoundError("Resource");
195
+ }
196
+ if (status === 429) {
197
+ const retryAfter = response.headers.get("Retry-After");
198
+ throw new RateLimitError(retryAfter ? parseInt(retryAfter, 10) : undefined);
199
+ }
200
+ // Try to get error details from response
201
+ let errorMessage = `HTTP ${status}`;
202
+ try {
203
+ const errorBody = await response.text();
204
+ if (errorBody) {
205
+ errorMessage += `: ${errorBody.slice(0, 200)}`;
206
+ }
207
+ }
208
+ catch {
209
+ // Ignore parse errors
210
+ }
211
+ throw new SkylightError(errorMessage, "HTTP_ERROR", status, status >= 500);
212
+ }
213
+ /**
214
+ * Make an authenticated request to the Skylight API
215
+ */
216
+ async request(endpoint, options = {}, isRetry = false) {
217
+ const { method = "GET", params, body, apiVersion } = options;
218
+ // Replace {frameId} placeholder with actual frame ID
219
+ const resolvedEndpoint = endpoint.replace("{frameId}", this.config.frameId);
220
+ const url = this.buildUrl(resolvedEndpoint, params);
221
+ console.error(`[client] ${method} ${url}`);
222
+ const headers = {
223
+ Authorization: await this.getAuthHeader(),
224
+ Accept: "application/json",
225
+ "User-Agent": "SkylightMobile (web)",
226
+ "Skylight-Api-Version": apiVersion ?? SKYLIGHT_API_VERSION,
227
+ };
228
+ if (body) {
229
+ headers["Content-Type"] = "application/json";
230
+ }
231
+ const response = await fetch(url, {
232
+ method,
233
+ headers,
234
+ body: body ? JSON.stringify(body) : undefined,
235
+ });
236
+ console.error(`[client] Response: ${response.status}`);
237
+ if (!response.ok) {
238
+ // Retry once on 401 with a fresh credential (not for an explicit token)
239
+ if (response.status === 401 && !this.config.token && !isRetry) {
240
+ console.error("[client] Got 401, refreshing credentials...");
241
+ this.resolvedToken = null;
242
+ if (this.stored)
243
+ this.stored = { ...this.stored, expiresAt: 0 };
244
+ return this.request(endpoint, options, true);
245
+ }
246
+ await this.handleResponseError(response, url);
247
+ }
248
+ // Handle 304 Not Modified
249
+ if (response.status === 304) {
250
+ return {};
251
+ }
252
+ // Some writes (a message delete) answer 200/204 with no body.
253
+ if (response.status === 204)
254
+ return {};
255
+ const raw = await response.text();
256
+ if (raw.trim() === "")
257
+ return {};
258
+ return JSON.parse(raw);
259
+ }
260
+ /**
261
+ * GET request helper
262
+ */
263
+ async get(endpoint, params) {
264
+ return this.request(endpoint, { method: "GET", params });
265
+ }
266
+ /**
267
+ * POST request helper
268
+ */
269
+ async post(endpoint, body) {
270
+ return this.request(endpoint, { method: "POST", body });
271
+ }
272
+ /**
273
+ * Get the frame ID from config
274
+ */
275
+ get frameId() {
276
+ return this.config.frameId;
277
+ }
278
+ /**
279
+ * Get the timezone from config
280
+ */
281
+ get timezone() {
282
+ return this.config.timezone;
283
+ }
284
+ /**
285
+ * Check if user has Plus subscription
286
+ */
287
+ hasPlus() {
288
+ return this.subscriptionStatus === "plus";
289
+ }
290
+ /**
291
+ * Get the subscription status
292
+ */
293
+ getSubscriptionStatus() {
294
+ return this.subscriptionStatus;
295
+ }
296
+ /**
297
+ * Initialize the client (triggers login if using email/password auth)
298
+ */
299
+ async initialize() {
300
+ await this.getCredentials();
301
+ }
302
+ }
303
+ // Process-wide instance for the single-user stdio server. A hosted server
304
+ // sets a per-request client via runWithContext() instead.
305
+ let clientInstance = null;
306
+ export function getClient() {
307
+ const context = currentContext();
308
+ if (context)
309
+ return context.client;
310
+ if (!clientInstance) {
311
+ throw new AuthenticationError("Skylight client has not been initialized.");
312
+ }
313
+ return clientInstance;
314
+ }
315
+ /**
316
+ * Initialize the process-wide client and return it.
317
+ * Resolves credentials (and logs in, for the deprecated email/password path).
318
+ */
319
+ export async function initializeClient(config, store = null) {
320
+ clientInstance = new SkylightClient(config, store);
321
+ await clientInstance.initialize();
322
+ return clientInstance;
323
+ }
324
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare const SKYLIGHT_BASE_URL = "https://app.ourskylight.com";
2
+ export declare const SKYLIGHT_WEB_APP_URL = "https://ourskylight.com";
3
+ export declare const SKYLIGHT_API_VERSION = "2026-03-01";
4
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/api/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,gCAAgC,CAAC;AAC/D,eAAO,MAAM,oBAAoB,4BAA4B,CAAC;AAC9D,eAAO,MAAM,oBAAoB,eAAe,CAAC"}
@@ -0,0 +1,4 @@
1
+ export const SKYLIGHT_BASE_URL = "https://app.ourskylight.com";
2
+ export const SKYLIGHT_WEB_APP_URL = "https://ourskylight.com";
3
+ export const SKYLIGHT_API_VERSION = "2026-03-01";
4
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/api/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,iBAAiB,GAAG,6BAA6B,CAAC;AAC/D,MAAM,CAAC,MAAM,oBAAoB,GAAG,yBAAyB,CAAC;AAC9D,MAAM,CAAC,MAAM,oBAAoB,GAAG,YAAY,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Local credential storage.
3
+ *
4
+ * macOS: the OAuth credential is AES-256-GCM encrypted into a 0600 file; the
5
+ * 256-bit key lives in the macOS Keychain, so the file alone is useless.
6
+ * Other platforms: a 0600 file inside a 0700 directory (no encryption; anything
7
+ * running as your user can read it, same as ~/.ssh keys).
8
+ *
9
+ * Neither store ever falls back silently to something weaker: if the Keychain
10
+ * cannot be used, writes fail with an error.
11
+ *
12
+ * Structure informed by kamilio/skylight-calendar-agent (MIT); see
13
+ * THIRD_PARTY_NOTICES.md.
14
+ */
15
+ import { CredentialStoreError, type CredentialStore } from "./credential-types.js";
16
+ import { type PendingLogin } from "./oauth.js";
17
+ export { CredentialStoreError, type CredentialStore };
18
+ export declare function defaultConfigDirectory(): string;
19
+ export declare function createFileCredentialStore(options?: {
20
+ directory?: string;
21
+ }): CredentialStore;
22
+ interface CommandResult {
23
+ code: number;
24
+ stdout: string;
25
+ stderr: string;
26
+ }
27
+ export type KeychainRunner = (args: readonly string[], input?: string) => Promise<CommandResult>;
28
+ export declare function createKeychainCredentialStore(options?: {
29
+ directory?: string;
30
+ runner?: KeychainRunner;
31
+ account?: string;
32
+ }): CredentialStore;
33
+ export declare function defaultCredentialStore(): CredentialStore;
34
+ /** Persist the PKCE verifier + state between `auth login` and `auth complete`. */
35
+ export declare function savePendingLogin(pending: PendingLogin, directory?: string): Promise<void>;
36
+ /** Read and delete the pending login (single use). Returns null if none. */
37
+ export declare function takePendingLogin(directory?: string): Promise<PendingLogin | null>;
38
+ //# sourceMappingURL=credential-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-store.d.ts","sourceRoot":"","sources":["../../src/api/credential-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,OAAO,EAAE,oBAAoB,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACnF,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAEtE,OAAO,EAAE,oBAAoB,EAAE,KAAK,eAAe,EAAE,CAAC;AAStD,wBAAgB,sBAAsB,IAAI,MAAM,CAQ/C;AAwCD,wBAAgB,yBAAyB,CAAC,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,eAAe,CAuB/F;AAID,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;AAgFjG,wBAAgB,6BAA6B,CAC3C,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9E,eAAe,CA2EjB;AAED,wBAAgB,sBAAsB,IAAI,eAAe,CAExD;AAID,kFAAkF;AAClF,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,YAAY,EAAE,SAAS,SAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CAGjH;AAED,4EAA4E;AAC5E,wBAAsB,gBAAgB,CAAC,SAAS,SAA2B,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAmBzG"}
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Local credential storage.
3
+ *
4
+ * macOS: the OAuth credential is AES-256-GCM encrypted into a 0600 file; the
5
+ * 256-bit key lives in the macOS Keychain, so the file alone is useless.
6
+ * Other platforms: a 0600 file inside a 0700 directory (no encryption; anything
7
+ * running as your user can read it, same as ~/.ssh keys).
8
+ *
9
+ * Neither store ever falls back silently to something weaker: if the Keychain
10
+ * cannot be used, writes fail with an error.
11
+ *
12
+ * Structure informed by kamilio/skylight-calendar-agent (MIT); see
13
+ * THIRD_PARTY_NOTICES.md.
14
+ */
15
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
16
+ import { spawn } from "node:child_process";
17
+ import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
18
+ import { homedir } from "node:os";
19
+ import path from "node:path";
20
+ import { SKYLIGHT_BASE_URL } from "./constants.js";
21
+ import { CredentialStoreError } from "./credential-types.js";
22
+ import { parseStoredCredential } from "./oauth.js";
23
+ export { CredentialStoreError };
24
+ const KEYCHAIN_SERVICE = "skylight-mcp.encryption-key";
25
+ const KEYCHAIN_LABEL = "skylight-mcp";
26
+ const KEYCHAIN_TIMEOUT_MS = 15_000;
27
+ const CREDENTIAL_FILE = "credential.json";
28
+ const ENCRYPTED_FILE = "credential.enc.json";
29
+ const PENDING_FILE = "pending-login.json";
30
+ export function defaultConfigDirectory() {
31
+ if (process.platform === "darwin") {
32
+ return path.join(homedir(), "Library", "Application Support", "skylight-mcp");
33
+ }
34
+ if (process.platform === "win32") {
35
+ return path.join(process.env.APPDATA ?? path.join(homedir(), "AppData", "Roaming"), "skylight-mcp");
36
+ }
37
+ return path.join(process.env.XDG_CONFIG_HOME ?? path.join(homedir(), ".config"), "skylight-mcp");
38
+ }
39
+ async function ensureDirectory(directory) {
40
+ await mkdir(directory, { recursive: true, mode: 0o700 });
41
+ await chmod(directory, 0o700);
42
+ }
43
+ /** Write atomically with 0600 permissions. */
44
+ async function writePrivateFile(destination, contents) {
45
+ const temporary = `${destination}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
46
+ try {
47
+ await writeFile(temporary, contents, { encoding: "utf8", mode: 0o600, flag: "wx" });
48
+ await rename(temporary, destination);
49
+ await chmod(destination, 0o600);
50
+ }
51
+ finally {
52
+ await rm(temporary, { force: true });
53
+ }
54
+ }
55
+ async function readIfExists(file) {
56
+ try {
57
+ return await readFile(file, "utf8");
58
+ }
59
+ catch (error) {
60
+ if (error.code === "ENOENT")
61
+ return null;
62
+ throw error;
63
+ }
64
+ }
65
+ async function removeIfExists(file) {
66
+ try {
67
+ await rm(file);
68
+ return true;
69
+ }
70
+ catch (error) {
71
+ if (error.code === "ENOENT")
72
+ return false;
73
+ throw error;
74
+ }
75
+ }
76
+ // ---------------------------------------------------------------- file store
77
+ export function createFileCredentialStore(options = {}) {
78
+ const directory = options.directory ?? defaultConfigDirectory();
79
+ const file = path.join(directory, CREDENTIAL_FILE);
80
+ return {
81
+ name: "credential file (0600)",
82
+ async read() {
83
+ const raw = await readIfExists(file);
84
+ if (raw === null)
85
+ return null;
86
+ try {
87
+ return parseStoredCredential(JSON.parse(raw));
88
+ }
89
+ catch {
90
+ throw new CredentialStoreError("The stored credential file is corrupt. Run `skylight-mcp auth login` again.");
91
+ }
92
+ },
93
+ async write(credential) {
94
+ await ensureDirectory(directory);
95
+ await writePrivateFile(file, `${JSON.stringify(credential)}\n`);
96
+ },
97
+ async delete() {
98
+ return removeIfExists(file);
99
+ },
100
+ };
101
+ }
102
+ function securityRunner(command = "/usr/bin/security") {
103
+ return (args, input) => new Promise((resolve, reject) => {
104
+ const child = spawn(command, [...args], { stdio: ["pipe", "pipe", "pipe"] });
105
+ let stdout = "";
106
+ let stderr = "";
107
+ const timer = setTimeout(() => {
108
+ child.kill();
109
+ reject(new CredentialStoreError("macOS Keychain operation timed out."));
110
+ }, KEYCHAIN_TIMEOUT_MS);
111
+ child.stdout.setEncoding("utf8");
112
+ child.stderr.setEncoding("utf8");
113
+ child.stdout.on("data", (chunk) => {
114
+ if (stdout.length < 20_000)
115
+ stdout += chunk;
116
+ });
117
+ child.stderr.on("data", (chunk) => {
118
+ if (stderr.length < 20_000)
119
+ stderr += chunk;
120
+ });
121
+ child.on("error", (error) => {
122
+ clearTimeout(timer);
123
+ reject(new CredentialStoreError(`macOS Keychain command failed: ${error.message}`));
124
+ });
125
+ child.on("close", (code) => {
126
+ clearTimeout(timer);
127
+ resolve({ code: code ?? 1, stdout, stderr });
128
+ });
129
+ child.stdin.end(input);
130
+ });
131
+ }
132
+ function encrypt(plaintext, key) {
133
+ const iv = randomBytes(12);
134
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
135
+ const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
136
+ return {
137
+ version: 1,
138
+ iv: iv.toString("base64"),
139
+ ciphertext: ciphertext.toString("base64"),
140
+ tag: cipher.getAuthTag().toString("base64"),
141
+ };
142
+ }
143
+ function decrypt(payload, key) {
144
+ try {
145
+ const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(payload.iv, "base64"));
146
+ decipher.setAuthTag(Buffer.from(payload.tag, "base64"));
147
+ return Buffer.concat([decipher.update(Buffer.from(payload.ciphertext, "base64")), decipher.final()]).toString("utf8");
148
+ }
149
+ catch {
150
+ throw new CredentialStoreError("The stored credential could not be decrypted. Run `skylight-mcp auth login` again.");
151
+ }
152
+ }
153
+ function parseEncrypted(raw) {
154
+ let parsed = null;
155
+ try {
156
+ parsed = JSON.parse(raw);
157
+ }
158
+ catch {
159
+ // handled below
160
+ }
161
+ if (!parsed ||
162
+ parsed.version !== 1 ||
163
+ typeof parsed.iv !== "string" ||
164
+ typeof parsed.ciphertext !== "string" ||
165
+ typeof parsed.tag !== "string") {
166
+ throw new CredentialStoreError("The encrypted credential file is invalid. Run `skylight-mcp auth login` again.");
167
+ }
168
+ return parsed;
169
+ }
170
+ export function createKeychainCredentialStore(options = {}) {
171
+ const directory = options.directory ?? defaultConfigDirectory();
172
+ const run = options.runner ?? securityRunner();
173
+ const account = options.account ?? SKYLIGHT_BASE_URL;
174
+ const file = path.join(directory, ENCRYPTED_FILE);
175
+ const readKey = async () => {
176
+ const result = await run(["find-generic-password", "-a", account, "-s", KEYCHAIN_SERVICE, "-w"]);
177
+ if (result.code === 44)
178
+ return null; // item not found
179
+ if (result.code !== 0) {
180
+ throw new CredentialStoreError(`Could not read the encryption key from macOS Keychain (exit ${result.code}).`);
181
+ }
182
+ const key = Buffer.from(result.stdout.replace(/[\r\n]+$/, ""), "base64");
183
+ if (key.length !== 32) {
184
+ throw new CredentialStoreError("The encryption key in macOS Keychain is invalid. Run `skylight-mcp auth logout`, then log in again.");
185
+ }
186
+ return key;
187
+ };
188
+ const writeKey = async (key) => {
189
+ const encoded = key.toString("base64");
190
+ // `security add-generic-password -w` with no value reads from the TTY, not
191
+ // stdin, so it hangs when run from a program. `security -i` instead reads
192
+ // whole commands from stdin, which keeps the key out of the process
193
+ // argument list. None of the values need quoting: the account is a URL,
194
+ // the service and label have no spaces, and the key is base64.
195
+ const result = await run(["-i"], `add-generic-password -U -a ${account} -s ${KEYCHAIN_SERVICE} -l ${KEYCHAIN_LABEL} -w ${encoded}\n`);
196
+ if (result.code !== 0) {
197
+ throw new CredentialStoreError(`Could not store the encryption key in macOS Keychain (exit ${result.code}).`);
198
+ }
199
+ // Interactive mode does not reliably report per-command failures, so
200
+ // confirm the key is really there before encrypting anything with it.
201
+ const stored = await readKey();
202
+ if (stored === null || !stored.equals(key)) {
203
+ throw new CredentialStoreError("macOS Keychain did not store the encryption key. Nothing was saved.");
204
+ }
205
+ };
206
+ return {
207
+ name: "macOS Keychain + encrypted file",
208
+ async read() {
209
+ const raw = await readIfExists(file);
210
+ if (raw === null)
211
+ return null;
212
+ const key = await readKey();
213
+ if (key === null) {
214
+ throw new CredentialStoreError("The encryption key is missing from macOS Keychain. Run `skylight-mcp auth login` again.");
215
+ }
216
+ const plaintext = decrypt(parseEncrypted(raw), key);
217
+ try {
218
+ return parseStoredCredential(JSON.parse(plaintext));
219
+ }
220
+ catch {
221
+ throw new CredentialStoreError("The stored credential is corrupt. Run `skylight-mcp auth login` again.");
222
+ }
223
+ },
224
+ async write(credential) {
225
+ let key = await readKey();
226
+ if (key === null) {
227
+ key = randomBytes(32);
228
+ await writeKey(key);
229
+ }
230
+ await ensureDirectory(directory);
231
+ await writePrivateFile(file, `${JSON.stringify(encrypt(JSON.stringify(credential), key))}\n`);
232
+ },
233
+ async delete() {
234
+ const removedFile = await removeIfExists(file);
235
+ const result = await run(["delete-generic-password", "-a", account, "-s", KEYCHAIN_SERVICE]);
236
+ if (result.code !== 0 && result.code !== 44) {
237
+ throw new CredentialStoreError(`Could not remove the encryption key from macOS Keychain (exit ${result.code}).`);
238
+ }
239
+ return removedFile || result.code === 0;
240
+ },
241
+ };
242
+ }
243
+ export function defaultCredentialStore() {
244
+ return process.platform === "darwin" ? createKeychainCredentialStore() : createFileCredentialStore();
245
+ }
246
+ // -------------------------------------------------------------- pending login
247
+ /** Persist the PKCE verifier + state between `auth login` and `auth complete`. */
248
+ export async function savePendingLogin(pending, directory = defaultConfigDirectory()) {
249
+ await ensureDirectory(directory);
250
+ await writePrivateFile(path.join(directory, PENDING_FILE), `${JSON.stringify(pending)}\n`);
251
+ }
252
+ /** Read and delete the pending login (single use). Returns null if none. */
253
+ export async function takePendingLogin(directory = defaultConfigDirectory()) {
254
+ const file = path.join(directory, PENDING_FILE);
255
+ const raw = await readIfExists(file);
256
+ if (raw === null)
257
+ return null;
258
+ await removeIfExists(file);
259
+ try {
260
+ const parsed = JSON.parse(raw);
261
+ if (typeof parsed.state === "string" &&
262
+ typeof parsed.codeVerifier === "string" &&
263
+ typeof parsed.fingerprint === "string" &&
264
+ typeof parsed.createdAt === "number") {
265
+ return parsed;
266
+ }
267
+ }
268
+ catch {
269
+ // fall through
270
+ }
271
+ return null;
272
+ }
273
+ //# sourceMappingURL=credential-store.js.map