@gethmy/mcp 2.13.4 → 2.15.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.
@@ -210,6 +210,140 @@ function getMemoryDir() {
210
210
  var DEFAULT_API_URL = "https://app.gethmy.com/api", LOCAL_CONFIG_FILENAME = ".harmony-mcp.json";
211
211
  var init_config = () => {};
212
212
 
213
+ // src/oauth-login.ts
214
+ function oauthBaseFromApiUrl(apiUrl) {
215
+ return `${new URL(apiUrl).origin}/oauth`;
216
+ }
217
+ var MCP_RESOURCE_URL, LOGIN_TIMEOUT_MS;
218
+ var init_oauth_login = __esm(() => {
219
+ MCP_RESOURCE_URL = process.env.HARMONY_MCP_RESOURCE || "https://mcp.gethmy.com";
220
+ LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
221
+ });
222
+
223
+ // src/oauth-refresh.ts
224
+ var exports_oauth_refresh = {};
225
+ __export(exports_oauth_refresh, {
226
+ refreshOAuthToken: () => refreshOAuthToken
227
+ });
228
+ import {
229
+ closeSync,
230
+ openSync,
231
+ renameSync,
232
+ rmSync,
233
+ statSync,
234
+ writeSync
235
+ } from "node:fs";
236
+ import { join as join2 } from "node:path";
237
+ function lockPath() {
238
+ return join2(getConfigDir(), LOCK_FILENAME);
239
+ }
240
+ function sleep(ms) {
241
+ return new Promise((resolve) => setTimeout(resolve, ms));
242
+ }
243
+ async function withRefreshLock(fn) {
244
+ const path = lockPath();
245
+ const deadline = Date.now() + LOCK_ACQUIRE_TIMEOUT_MS;
246
+ let held = false;
247
+ while (Date.now() < deadline) {
248
+ try {
249
+ const fd = openSync(path, "wx");
250
+ writeSync(fd, String(process.pid));
251
+ closeSync(fd);
252
+ held = true;
253
+ break;
254
+ } catch (err) {
255
+ if (err.code !== "EEXIST") {
256
+ break;
257
+ }
258
+ try {
259
+ const age = Date.now() - statSync(path).mtimeMs;
260
+ if (age > LOCK_STALE_MS) {
261
+ const claim = `${path}.stale.${process.pid}`;
262
+ try {
263
+ renameSync(path, claim);
264
+ rmSync(claim, { force: true });
265
+ } catch {}
266
+ continue;
267
+ }
268
+ } catch {
269
+ continue;
270
+ }
271
+ await sleep(LOCK_RETRY_MS);
272
+ }
273
+ }
274
+ try {
275
+ return await fn();
276
+ } finally {
277
+ if (held) {
278
+ try {
279
+ rmSync(path, { force: true });
280
+ } catch {}
281
+ }
282
+ }
283
+ }
284
+ function refreshOAuthToken() {
285
+ if (inFlight)
286
+ return inFlight;
287
+ inFlight = doRefresh().finally(() => {
288
+ inFlight = null;
289
+ });
290
+ return inFlight;
291
+ }
292
+ async function doRefresh() {
293
+ const before = loadConfig();
294
+ if (!before.oauthRefreshToken || !before.oauthClientId)
295
+ return null;
296
+ return withRefreshLock(async () => {
297
+ const config = loadConfig();
298
+ if (config.oauthRefreshToken !== before.oauthRefreshToken && config.oauthAccessToken) {
299
+ return config.oauthAccessToken;
300
+ }
301
+ if (!config.oauthRefreshToken || !config.oauthClientId)
302
+ return null;
303
+ const base = oauthBaseFromApiUrl(config.apiUrl);
304
+ let res;
305
+ try {
306
+ res = await fetch(`${base}/token`, {
307
+ method: "POST",
308
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
309
+ body: new URLSearchParams({
310
+ grant_type: "refresh_token",
311
+ refresh_token: config.oauthRefreshToken,
312
+ client_id: config.oauthClientId
313
+ }).toString()
314
+ });
315
+ } catch {
316
+ return null;
317
+ }
318
+ if (!res.ok) {
319
+ const errorCode = await res.json().then((b) => b?.error ?? null).catch(() => null);
320
+ if (errorCode === "invalid_grant") {
321
+ saveConfig({
322
+ oauthAccessToken: null,
323
+ oauthRefreshToken: null,
324
+ oauthExpiresAt: null,
325
+ oauthClientId: null
326
+ });
327
+ }
328
+ return null;
329
+ }
330
+ const body = await res.json().catch(() => null);
331
+ if (!body?.access_token || !body.refresh_token)
332
+ return null;
333
+ saveConfig({
334
+ oauthAccessToken: body.access_token,
335
+ oauthRefreshToken: body.refresh_token,
336
+ oauthExpiresAt: Date.now() + (body.expires_in ?? 0) * 1000
337
+ });
338
+ return body.access_token;
339
+ });
340
+ }
341
+ var inFlight = null, LOCK_FILENAME = "refresh.lock", LOCK_STALE_MS = 30000, LOCK_ACQUIRE_TIMEOUT_MS = 35000, LOCK_RETRY_MS = 100;
342
+ var init_oauth_refresh = __esm(() => {
343
+ init_config();
344
+ init_oauth_login();
345
+ });
346
+
213
347
  // src/prompt-builder.ts
214
348
  var exports_prompt_builder = {};
215
349
  __export(exports_prompt_builder, {
@@ -699,140 +833,6 @@ var init_prompt_builder = __esm(() => {
699
833
  execute: `EXECUTE MODE: Implement this task completely. Write production-ready code following best practices. Include necessary tests and documentation.`
700
834
  };
701
835
  });
702
-
703
- // src/oauth-login.ts
704
- function oauthBaseFromApiUrl(apiUrl) {
705
- return `${new URL(apiUrl).origin}/oauth`;
706
- }
707
- var MCP_RESOURCE_URL, LOGIN_TIMEOUT_MS;
708
- var init_oauth_login = __esm(() => {
709
- MCP_RESOURCE_URL = process.env.HARMONY_MCP_RESOURCE || "https://mcp.gethmy.com";
710
- LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
711
- });
712
-
713
- // src/oauth-refresh.ts
714
- var exports_oauth_refresh = {};
715
- __export(exports_oauth_refresh, {
716
- refreshOAuthToken: () => refreshOAuthToken
717
- });
718
- import {
719
- closeSync,
720
- openSync,
721
- renameSync,
722
- rmSync,
723
- statSync,
724
- writeSync
725
- } from "node:fs";
726
- import { join as join2 } from "node:path";
727
- function lockPath() {
728
- return join2(getConfigDir(), LOCK_FILENAME);
729
- }
730
- function sleep(ms) {
731
- return new Promise((resolve) => setTimeout(resolve, ms));
732
- }
733
- async function withRefreshLock(fn) {
734
- const path = lockPath();
735
- const deadline = Date.now() + LOCK_ACQUIRE_TIMEOUT_MS;
736
- let held = false;
737
- while (Date.now() < deadline) {
738
- try {
739
- const fd = openSync(path, "wx");
740
- writeSync(fd, String(process.pid));
741
- closeSync(fd);
742
- held = true;
743
- break;
744
- } catch (err) {
745
- if (err.code !== "EEXIST") {
746
- break;
747
- }
748
- try {
749
- const age = Date.now() - statSync(path).mtimeMs;
750
- if (age > LOCK_STALE_MS) {
751
- const claim = `${path}.stale.${process.pid}`;
752
- try {
753
- renameSync(path, claim);
754
- rmSync(claim, { force: true });
755
- } catch {}
756
- continue;
757
- }
758
- } catch {
759
- continue;
760
- }
761
- await sleep(LOCK_RETRY_MS);
762
- }
763
- }
764
- try {
765
- return await fn();
766
- } finally {
767
- if (held) {
768
- try {
769
- rmSync(path, { force: true });
770
- } catch {}
771
- }
772
- }
773
- }
774
- function refreshOAuthToken() {
775
- if (inFlight)
776
- return inFlight;
777
- inFlight = doRefresh().finally(() => {
778
- inFlight = null;
779
- });
780
- return inFlight;
781
- }
782
- async function doRefresh() {
783
- const before = loadConfig();
784
- if (!before.oauthRefreshToken || !before.oauthClientId)
785
- return null;
786
- return withRefreshLock(async () => {
787
- const config = loadConfig();
788
- if (config.oauthRefreshToken !== before.oauthRefreshToken && config.oauthAccessToken) {
789
- return config.oauthAccessToken;
790
- }
791
- if (!config.oauthRefreshToken || !config.oauthClientId)
792
- return null;
793
- const base = oauthBaseFromApiUrl(config.apiUrl);
794
- let res;
795
- try {
796
- res = await fetch(`${base}/token`, {
797
- method: "POST",
798
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
799
- body: new URLSearchParams({
800
- grant_type: "refresh_token",
801
- refresh_token: config.oauthRefreshToken,
802
- client_id: config.oauthClientId
803
- }).toString()
804
- });
805
- } catch {
806
- return null;
807
- }
808
- if (!res.ok) {
809
- const errorCode = await res.json().then((b) => b?.error ?? null).catch(() => null);
810
- if (errorCode === "invalid_grant") {
811
- saveConfig({
812
- oauthAccessToken: null,
813
- oauthRefreshToken: null,
814
- oauthExpiresAt: null,
815
- oauthClientId: null
816
- });
817
- }
818
- return null;
819
- }
820
- const body = await res.json().catch(() => null);
821
- if (!body?.access_token || !body.refresh_token)
822
- return null;
823
- saveConfig({
824
- oauthAccessToken: body.access_token,
825
- oauthRefreshToken: body.refresh_token,
826
- oauthExpiresAt: Date.now() + (body.expires_in ?? 0) * 1000
827
- });
828
- return body.access_token;
829
- });
830
- }
831
- var inFlight = null, LOCK_FILENAME = "refresh.lock", LOCK_STALE_MS = 30000, LOCK_ACQUIRE_TIMEOUT_MS = 35000, LOCK_RETRY_MS = 100;
832
- var init_oauth_refresh = __esm(() => {
833
- init_config();
834
- init_oauth_login();
835
- });
836
836
  // ../harmony-shared/dist/cardLinks.js
837
837
  var LINK_TYPE_INVERSES = {
838
838
  relates_to: "relates_to",
@@ -904,6 +904,8 @@ function serializeCommentThread(comments, options = {}) {
904
904
  const tags = [];
905
905
  if (c.edited_at)
906
906
  tags.push("edited");
907
+ if (c.reply_to_id)
908
+ tags.push(`reply to ${ref(c.reply_to_id)}`);
907
909
  if (c.supersedes_id)
908
910
  tags.push(`supersedes ${ref(c.supersedes_id)}`);
909
911
  if (c.confirms_id)
@@ -1238,6 +1240,33 @@ class HarmonyApiClient {
1238
1240
  const query = params.toString() ? `?${params.toString()}` : "";
1239
1241
  return this.request("GET", `/board/${projectId}${query}`);
1240
1242
  }
1243
+ async getFullBoard(projectId, options) {
1244
+ const { pageSize = 200, ...boardOpts } = options ?? {};
1245
+ const cards = [];
1246
+ let offset = 0;
1247
+ let last = null;
1248
+ for (;; ) {
1249
+ const page = await this.getBoard(projectId, {
1250
+ ...boardOpts,
1251
+ limit: pageSize,
1252
+ offset
1253
+ });
1254
+ last = page;
1255
+ const pageCards = page.cards ?? [];
1256
+ cards.push(...pageCards);
1257
+ const hasMore = page.pagination?.hasMore ?? false;
1258
+ if (!hasMore || pageCards.length === 0)
1259
+ break;
1260
+ offset += pageSize;
1261
+ }
1262
+ return {
1263
+ project: last?.project,
1264
+ columns: last?.columns ?? [],
1265
+ cards,
1266
+ labels: last?.labels ?? [],
1267
+ totalCards: last?.pagination?.totalCards ?? cards.length
1268
+ };
1269
+ }
1241
1270
  async createCard(projectId, data) {
1242
1271
  return this.request("POST", "/cards", { projectId, ...data });
1243
1272
  }
@@ -1315,6 +1344,12 @@ class HarmonyApiClient {
1315
1344
  async getCardExternalLinks(cardId) {
1316
1345
  return this.request("GET", `/cards/${cardId}/external-links`);
1317
1346
  }
1347
+ async addExternalLink(cardId, url, title) {
1348
+ return this.request("POST", `/cards/${cardId}/external-links`, {
1349
+ url,
1350
+ title
1351
+ });
1352
+ }
1318
1353
  async uploadArtifact(data) {
1319
1354
  return this.request("POST", "/artifacts", data);
1320
1355
  }
@@ -1366,6 +1401,7 @@ class HarmonyApiClient {
1366
1401
  commentType: opts?.commentType,
1367
1402
  supersedesId: opts?.supersedesId,
1368
1403
  confirmsId: opts?.confirmsId,
1404
+ replyToId: opts?.replyToId,
1369
1405
  agentSessionId: opts?.agentSessionId
1370
1406
  });
1371
1407
  }
@@ -1818,12 +1854,6 @@ ${planContent.trim()}`;
1818
1854
  async updatePlaybook(playbookId, updates) {
1819
1855
  return this.request("PATCH", `/playbooks/${playbookId}`, updates);
1820
1856
  }
1821
- async runPlaybook(playbookId) {
1822
- return this.request("POST", `/playbooks/${playbookId}/run`);
1823
- }
1824
- async savePlaybookFromCard(data) {
1825
- return this.request("POST", "/playbooks/from-card", data);
1826
- }
1827
1857
  }
1828
1858
  var _promptModules = null;
1829
1859
  async function loadPromptModules() {
@@ -0,0 +1,350 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __returnValue = (v) => v;
3
+ function __exportSetter(name, newValue) {
4
+ this[name] = __returnValue.bind(null, newValue);
5
+ }
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, {
9
+ get: all[name],
10
+ enumerable: true,
11
+ configurable: true,
12
+ set: __exportSetter.bind(all, name)
13
+ });
14
+ };
15
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
16
+
17
+ // src/config.ts
18
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
19
+ import { homedir } from "node:os";
20
+ import { join } from "node:path";
21
+ function getConfigDir() {
22
+ return join(homedir(), ".harmony-mcp");
23
+ }
24
+ function getConfigPath() {
25
+ return join(getConfigDir(), "config.json");
26
+ }
27
+ function getLocalConfigPath(cwd) {
28
+ return join(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
29
+ }
30
+ function emptyConfig() {
31
+ return {
32
+ apiKey: null,
33
+ apiUrl: DEFAULT_API_URL,
34
+ activeWorkspaceId: null,
35
+ activeProjectId: null,
36
+ userEmail: null,
37
+ memoryDir: null,
38
+ oauthAccessToken: null,
39
+ oauthRefreshToken: null,
40
+ oauthExpiresAt: null,
41
+ oauthClientId: null
42
+ };
43
+ }
44
+ function loadConfig() {
45
+ const configPath = getConfigPath();
46
+ if (!existsSync(configPath)) {
47
+ return emptyConfig();
48
+ }
49
+ try {
50
+ const data = readFileSync(configPath, "utf-8");
51
+ const config = JSON.parse(data);
52
+ return {
53
+ apiKey: config.apiKey || null,
54
+ apiUrl: config.apiUrl || DEFAULT_API_URL,
55
+ activeWorkspaceId: config.activeWorkspaceId || null,
56
+ activeProjectId: config.activeProjectId || null,
57
+ userEmail: config.userEmail || null,
58
+ memoryDir: config.memoryDir || null,
59
+ oauthAccessToken: config.oauthAccessToken || null,
60
+ oauthRefreshToken: config.oauthRefreshToken || null,
61
+ oauthExpiresAt: typeof config.oauthExpiresAt === "number" ? config.oauthExpiresAt : null,
62
+ oauthClientId: config.oauthClientId || null
63
+ };
64
+ } catch {
65
+ return emptyConfig();
66
+ }
67
+ }
68
+ function saveConfig(config) {
69
+ const configDir = getConfigDir();
70
+ const configPath = getConfigPath();
71
+ if (!existsSync(configDir)) {
72
+ mkdirSync(configDir, { recursive: true, mode: 448 });
73
+ }
74
+ const existingConfig = loadConfig();
75
+ const newConfig = { ...existingConfig, ...config };
76
+ writeFileSync(configPath, JSON.stringify(newConfig, null, 2), {
77
+ mode: 384
78
+ });
79
+ }
80
+ function loadLocalConfig(cwd) {
81
+ const localConfigPath = getLocalConfigPath(cwd);
82
+ if (!existsSync(localConfigPath)) {
83
+ return null;
84
+ }
85
+ try {
86
+ const data = readFileSync(localConfigPath, "utf-8");
87
+ const config = JSON.parse(data);
88
+ return {
89
+ workspaceId: config.workspaceId || null,
90
+ projectId: config.projectId || null
91
+ };
92
+ } catch {
93
+ return null;
94
+ }
95
+ }
96
+ function saveLocalConfig(config, cwd) {
97
+ const localConfigPath = getLocalConfigPath(cwd);
98
+ const existingConfig = loadLocalConfig(cwd) || {
99
+ workspaceId: null,
100
+ projectId: null
101
+ };
102
+ const newConfig = { ...existingConfig, ...config };
103
+ const cleanConfig = {};
104
+ if (newConfig.workspaceId)
105
+ cleanConfig.workspaceId = newConfig.workspaceId;
106
+ if (newConfig.projectId)
107
+ cleanConfig.projectId = newConfig.projectId;
108
+ writeFileSync(localConfigPath, JSON.stringify(cleanConfig, null, 2));
109
+ }
110
+ function hasLocalConfig(cwd) {
111
+ return existsSync(getLocalConfigPath(cwd));
112
+ }
113
+ function getActiveCredential() {
114
+ const config = loadConfig();
115
+ if (config.oauthAccessToken)
116
+ return config.oauthAccessToken;
117
+ if (config.apiKey)
118
+ return config.apiKey;
119
+ throw new Error(`Not configured. Run "npx @gethmy/mcp setup" to connect Harmony.
120
+ ` + "Setup authorizes in your browser — no API key handling required.");
121
+ }
122
+ function getApiKey() {
123
+ return getActiveCredential();
124
+ }
125
+ function getApiUrl() {
126
+ const config = loadConfig();
127
+ return config.apiUrl;
128
+ }
129
+ function getUserEmail() {
130
+ const config = loadConfig();
131
+ return config.userEmail;
132
+ }
133
+ function setUserEmail(email) {
134
+ saveConfig({ userEmail: email });
135
+ }
136
+ function setActiveWorkspace(workspaceId, options) {
137
+ if (options?.local) {
138
+ saveLocalConfig({ workspaceId }, options.cwd);
139
+ } else {
140
+ saveConfig({ activeWorkspaceId: workspaceId });
141
+ }
142
+ }
143
+ function setActiveProject(projectId, options) {
144
+ if (options?.local) {
145
+ saveLocalConfig({ projectId }, options.cwd);
146
+ } else {
147
+ saveConfig({ activeProjectId: projectId });
148
+ }
149
+ }
150
+ function getActiveWorkspaceId(cwd) {
151
+ const localConfig = loadLocalConfig(cwd);
152
+ if (localConfig?.workspaceId) {
153
+ return localConfig.workspaceId;
154
+ }
155
+ return loadConfig().activeWorkspaceId;
156
+ }
157
+ function getActiveProjectId(cwd) {
158
+ const localConfig = loadLocalConfig(cwd);
159
+ if (localConfig?.projectId) {
160
+ return localConfig.projectId;
161
+ }
162
+ return loadConfig().activeProjectId;
163
+ }
164
+ function isConfigured() {
165
+ const config = loadConfig();
166
+ return !!(config.apiKey || config.oauthAccessToken);
167
+ }
168
+ function areSkillsInstalled(cwd) {
169
+ const home = homedir();
170
+ const workingDir = cwd || process.cwd();
171
+ const foundPaths = [];
172
+ const globalSkillsDir = join(home, ".agents", "skills");
173
+ const globalSkillPath = join(globalSkillsDir, "hmy", "SKILL.md");
174
+ if (existsSync(globalSkillPath)) {
175
+ foundPaths.push(globalSkillPath);
176
+ return { installed: true, location: "global", paths: foundPaths };
177
+ }
178
+ const claudeGlobalSkill = join(home, ".claude", "skills", "hmy.md");
179
+ if (existsSync(claudeGlobalSkill)) {
180
+ foundPaths.push(claudeGlobalSkill);
181
+ return { installed: true, location: "global", paths: foundPaths };
182
+ }
183
+ const claudeGlobalSkillAlt = join(home, ".claude", "skills", "hmy", "SKILL.md");
184
+ if (existsSync(claudeGlobalSkillAlt)) {
185
+ foundPaths.push(claudeGlobalSkillAlt);
186
+ return { installed: true, location: "global", paths: foundPaths };
187
+ }
188
+ const localSkillPath = join(workingDir, ".claude", "skills", "hmy.md");
189
+ if (existsSync(localSkillPath)) {
190
+ foundPaths.push(localSkillPath);
191
+ return { installed: true, location: "local", paths: foundPaths };
192
+ }
193
+ const localSkillPathAlt = join(workingDir, ".claude", "skills", "hmy", "SKILL.md");
194
+ if (existsSync(localSkillPathAlt)) {
195
+ foundPaths.push(localSkillPathAlt);
196
+ return { installed: true, location: "local", paths: foundPaths };
197
+ }
198
+ return { installed: false, location: null, paths: [] };
199
+ }
200
+ function hasProjectContext(cwd) {
201
+ const localConfig = loadLocalConfig(cwd);
202
+ return !!(localConfig?.workspaceId || localConfig?.projectId);
203
+ }
204
+ function getMemoryDir() {
205
+ const config = loadConfig();
206
+ if (config.memoryDir)
207
+ return config.memoryDir;
208
+ return join(homedir(), ".harmony", "memory");
209
+ }
210
+ var DEFAULT_API_URL = "https://app.gethmy.com/api", LOCAL_CONFIG_FILENAME = ".harmony-mcp.json";
211
+ var init_config = () => {};
212
+
213
+ // src/oauth-login.ts
214
+ function oauthBaseFromApiUrl(apiUrl) {
215
+ return `${new URL(apiUrl).origin}/oauth`;
216
+ }
217
+ var MCP_RESOURCE_URL, LOGIN_TIMEOUT_MS;
218
+ var init_oauth_login = __esm(() => {
219
+ MCP_RESOURCE_URL = process.env.HARMONY_MCP_RESOURCE || "https://mcp.gethmy.com";
220
+ LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
221
+ });
222
+
223
+ // src/oauth-refresh.ts
224
+ var exports_oauth_refresh = {};
225
+ __export(exports_oauth_refresh, {
226
+ refreshOAuthToken: () => refreshOAuthToken
227
+ });
228
+ import {
229
+ closeSync,
230
+ openSync,
231
+ renameSync,
232
+ rmSync,
233
+ statSync,
234
+ writeSync
235
+ } from "node:fs";
236
+ import { join as join2 } from "node:path";
237
+ function lockPath() {
238
+ return join2(getConfigDir(), LOCK_FILENAME);
239
+ }
240
+ function sleep(ms) {
241
+ return new Promise((resolve) => setTimeout(resolve, ms));
242
+ }
243
+ async function withRefreshLock(fn) {
244
+ const path = lockPath();
245
+ const deadline = Date.now() + LOCK_ACQUIRE_TIMEOUT_MS;
246
+ let held = false;
247
+ while (Date.now() < deadline) {
248
+ try {
249
+ const fd = openSync(path, "wx");
250
+ writeSync(fd, String(process.pid));
251
+ closeSync(fd);
252
+ held = true;
253
+ break;
254
+ } catch (err) {
255
+ if (err.code !== "EEXIST") {
256
+ break;
257
+ }
258
+ try {
259
+ const age = Date.now() - statSync(path).mtimeMs;
260
+ if (age > LOCK_STALE_MS) {
261
+ const claim = `${path}.stale.${process.pid}`;
262
+ try {
263
+ renameSync(path, claim);
264
+ rmSync(claim, { force: true });
265
+ } catch {}
266
+ continue;
267
+ }
268
+ } catch {
269
+ continue;
270
+ }
271
+ await sleep(LOCK_RETRY_MS);
272
+ }
273
+ }
274
+ try {
275
+ return await fn();
276
+ } finally {
277
+ if (held) {
278
+ try {
279
+ rmSync(path, { force: true });
280
+ } catch {}
281
+ }
282
+ }
283
+ }
284
+ function refreshOAuthToken() {
285
+ if (inFlight)
286
+ return inFlight;
287
+ inFlight = doRefresh().finally(() => {
288
+ inFlight = null;
289
+ });
290
+ return inFlight;
291
+ }
292
+ async function doRefresh() {
293
+ const before = loadConfig();
294
+ if (!before.oauthRefreshToken || !before.oauthClientId)
295
+ return null;
296
+ return withRefreshLock(async () => {
297
+ const config = loadConfig();
298
+ if (config.oauthRefreshToken !== before.oauthRefreshToken && config.oauthAccessToken) {
299
+ return config.oauthAccessToken;
300
+ }
301
+ if (!config.oauthRefreshToken || !config.oauthClientId)
302
+ return null;
303
+ const base = oauthBaseFromApiUrl(config.apiUrl);
304
+ let res;
305
+ try {
306
+ res = await fetch(`${base}/token`, {
307
+ method: "POST",
308
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
309
+ body: new URLSearchParams({
310
+ grant_type: "refresh_token",
311
+ refresh_token: config.oauthRefreshToken,
312
+ client_id: config.oauthClientId
313
+ }).toString()
314
+ });
315
+ } catch {
316
+ return null;
317
+ }
318
+ if (!res.ok) {
319
+ const errorCode = await res.json().then((b) => b?.error ?? null).catch(() => null);
320
+ if (errorCode === "invalid_grant") {
321
+ saveConfig({
322
+ oauthAccessToken: null,
323
+ oauthRefreshToken: null,
324
+ oauthExpiresAt: null,
325
+ oauthClientId: null
326
+ });
327
+ }
328
+ return null;
329
+ }
330
+ const body = await res.json().catch(() => null);
331
+ if (!body?.access_token || !body.refresh_token)
332
+ return null;
333
+ saveConfig({
334
+ oauthAccessToken: body.access_token,
335
+ oauthRefreshToken: body.refresh_token,
336
+ oauthExpiresAt: Date.now() + (body.expires_in ?? 0) * 1000
337
+ });
338
+ return body.access_token;
339
+ });
340
+ }
341
+ var inFlight = null, LOCK_FILENAME = "refresh.lock", LOCK_STALE_MS = 30000, LOCK_ACQUIRE_TIMEOUT_MS = 35000, LOCK_RETRY_MS = 100;
342
+ var init_oauth_refresh = __esm(() => {
343
+ init_config();
344
+ init_oauth_login();
345
+ });
346
+ init_oauth_refresh();
347
+
348
+ export {
349
+ refreshOAuthToken
350
+ };