@gethmy/mcp 2.9.3 → 2.9.5

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.
@@ -14,6 +14,202 @@ var __export = (target, all) => {
14
14
  };
15
15
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
16
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
+
17
213
  // src/prompt-builder.ts
18
214
  var exports_prompt_builder = {};
19
215
  __export(exports_prompt_builder, {
@@ -504,193 +700,139 @@ var init_prompt_builder = __esm(() => {
504
700
  };
505
701
  });
506
702
 
507
- // src/config.ts
508
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
509
- import { homedir } from "node:os";
510
- import { join } from "node:path";
511
- var DEFAULT_API_URL = "https://app.gethmy.com/api";
512
- var LOCAL_CONFIG_FILENAME = ".harmony-mcp.json";
513
- function getConfigDir() {
514
- return join(homedir(), ".harmony-mcp");
703
+ // src/oauth-login.ts
704
+ function oauthBaseFromApiUrl(apiUrl) {
705
+ return `${new URL(apiUrl).origin}/oauth`;
515
706
  }
516
- function getConfigPath() {
517
- return join(getConfigDir(), "config.json");
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);
518
729
  }
519
- function getLocalConfigPath(cwd) {
520
- return join(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
730
+ function sleep(ms) {
731
+ return new Promise((resolve) => setTimeout(resolve, ms));
521
732
  }
522
- function loadConfig() {
523
- const configPath = getConfigPath();
524
- if (!existsSync(configPath)) {
525
- return {
526
- apiKey: null,
527
- apiUrl: DEFAULT_API_URL,
528
- activeWorkspaceId: null,
529
- activeProjectId: null,
530
- userEmail: null,
531
- memoryDir: null
532
- };
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
+ }
533
763
  }
534
764
  try {
535
- const data = readFileSync(configPath, "utf-8");
536
- const config = JSON.parse(data);
537
- return {
538
- apiKey: config.apiKey || null,
539
- apiUrl: config.apiUrl || DEFAULT_API_URL,
540
- activeWorkspaceId: config.activeWorkspaceId || null,
541
- activeProjectId: config.activeProjectId || null,
542
- userEmail: config.userEmail || null,
543
- memoryDir: config.memoryDir || null
544
- };
545
- } catch {
546
- return {
547
- apiKey: null,
548
- apiUrl: DEFAULT_API_URL,
549
- activeWorkspaceId: null,
550
- activeProjectId: null,
551
- userEmail: null,
552
- memoryDir: null
553
- };
765
+ return await fn();
766
+ } finally {
767
+ if (held) {
768
+ try {
769
+ rmSync(path, { force: true });
770
+ } catch {}
771
+ }
554
772
  }
555
773
  }
556
- function saveConfig(config) {
557
- const configDir = getConfigDir();
558
- const configPath = getConfigPath();
559
- if (!existsSync(configDir)) {
560
- mkdirSync(configDir, { recursive: true, mode: 448 });
561
- }
562
- const existingConfig = loadConfig();
563
- const newConfig = { ...existingConfig, ...config };
564
- writeFileSync(configPath, JSON.stringify(newConfig, null, 2), {
565
- mode: 384
774
+ function refreshOAuthToken() {
775
+ if (inFlight)
776
+ return inFlight;
777
+ inFlight = doRefresh().finally(() => {
778
+ inFlight = null;
566
779
  });
780
+ return inFlight;
567
781
  }
568
- function loadLocalConfig(cwd) {
569
- const localConfigPath = getLocalConfigPath(cwd);
570
- if (!existsSync(localConfigPath)) {
571
- return null;
572
- }
573
- try {
574
- const data = readFileSync(localConfigPath, "utf-8");
575
- const config = JSON.parse(data);
576
- return {
577
- workspaceId: config.workspaceId || null,
578
- projectId: config.projectId || null
579
- };
580
- } catch {
782
+ async function doRefresh() {
783
+ const before = loadConfig();
784
+ if (!before.oauthRefreshToken || !before.oauthClientId)
581
785
  return null;
582
- }
583
- }
584
- function saveLocalConfig(config, cwd) {
585
- const localConfigPath = getLocalConfigPath(cwd);
586
- const existingConfig = loadLocalConfig(cwd) || {
587
- workspaceId: null,
588
- projectId: null
589
- };
590
- const newConfig = { ...existingConfig, ...config };
591
- const cleanConfig = {};
592
- if (newConfig.workspaceId)
593
- cleanConfig.workspaceId = newConfig.workspaceId;
594
- if (newConfig.projectId)
595
- cleanConfig.projectId = newConfig.projectId;
596
- writeFileSync(localConfigPath, JSON.stringify(cleanConfig, null, 2));
597
- }
598
- function hasLocalConfig(cwd) {
599
- return existsSync(getLocalConfigPath(cwd));
600
- }
601
- function getApiKey() {
602
- const config = loadConfig();
603
- if (!config.apiKey) {
604
- throw new Error(`Not configured. Run "npx @gethmy/mcp setup" to set your API key.
605
- ` + "You can generate an API key at https://gethmy.com Settings → API Keys.");
606
- }
607
- return config.apiKey;
608
- }
609
- function getApiUrl() {
610
- const config = loadConfig();
611
- return config.apiUrl;
612
- }
613
- function getUserEmail() {
614
- const config = loadConfig();
615
- return config.userEmail;
616
- }
617
- function setUserEmail(email) {
618
- saveConfig({ userEmail: email });
619
- }
620
- function setActiveWorkspace(workspaceId, options) {
621
- if (options?.local) {
622
- saveLocalConfig({ workspaceId }, options.cwd);
623
- } else {
624
- saveConfig({ activeWorkspaceId: workspaceId });
625
- }
626
- }
627
- function setActiveProject(projectId, options) {
628
- if (options?.local) {
629
- saveLocalConfig({ projectId }, options.cwd);
630
- } else {
631
- saveConfig({ activeProjectId: projectId });
632
- }
633
- }
634
- function getActiveWorkspaceId(cwd) {
635
- const localConfig = loadLocalConfig(cwd);
636
- if (localConfig?.workspaceId) {
637
- return localConfig.workspaceId;
638
- }
639
- return loadConfig().activeWorkspaceId;
640
- }
641
- function getActiveProjectId(cwd) {
642
- const localConfig = loadLocalConfig(cwd);
643
- if (localConfig?.projectId) {
644
- return localConfig.projectId;
645
- }
646
- return loadConfig().activeProjectId;
647
- }
648
- function isConfigured() {
649
- const config = loadConfig();
650
- return !!config.apiKey;
651
- }
652
- function areSkillsInstalled(cwd) {
653
- const home = homedir();
654
- const workingDir = cwd || process.cwd();
655
- const foundPaths = [];
656
- const globalSkillsDir = join(home, ".agents", "skills");
657
- const globalSkillPath = join(globalSkillsDir, "hmy", "SKILL.md");
658
- if (existsSync(globalSkillPath)) {
659
- foundPaths.push(globalSkillPath);
660
- return { installed: true, location: "global", paths: foundPaths };
661
- }
662
- const claudeGlobalSkill = join(home, ".claude", "skills", "hmy.md");
663
- if (existsSync(claudeGlobalSkill)) {
664
- foundPaths.push(claudeGlobalSkill);
665
- return { installed: true, location: "global", paths: foundPaths };
666
- }
667
- const claudeGlobalSkillAlt = join(home, ".claude", "skills", "hmy", "SKILL.md");
668
- if (existsSync(claudeGlobalSkillAlt)) {
669
- foundPaths.push(claudeGlobalSkillAlt);
670
- return { installed: true, location: "global", paths: foundPaths };
671
- }
672
- const localSkillPath = join(workingDir, ".claude", "skills", "hmy.md");
673
- if (existsSync(localSkillPath)) {
674
- foundPaths.push(localSkillPath);
675
- return { installed: true, location: "local", paths: foundPaths };
676
- }
677
- const localSkillPathAlt = join(workingDir, ".claude", "skills", "hmy", "SKILL.md");
678
- if (existsSync(localSkillPathAlt)) {
679
- foundPaths.push(localSkillPathAlt);
680
- return { installed: true, location: "local", paths: foundPaths };
681
- }
682
- return { installed: false, location: null, paths: [] };
683
- }
684
- function hasProjectContext(cwd) {
685
- const localConfig = loadLocalConfig(cwd);
686
- return !!(localConfig?.workspaceId || localConfig?.projectId);
687
- }
688
- function getMemoryDir() {
689
- const config = loadConfig();
690
- if (config.memoryDir)
691
- return config.memoryDir;
692
- return join(homedir(), ".harmony", "memory");
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
+ });
693
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
+ });
694
836
 
695
837
  // ../harmony-shared/dist/cardLinks.js
696
838
  var LINK_TYPE_INVERSES = {
@@ -804,6 +946,7 @@ var TIMINGS = {
804
946
  QUERY_GC_TIME: 1000 * 60 * 60 * 24
805
947
  };
806
948
  // src/api-client.ts
949
+ init_config();
807
950
  var RETRY_CONFIG = {
808
951
  maxRetries: 3,
809
952
  baseDelayMs: 1000,
@@ -823,7 +966,7 @@ function getRetryDelay(attempt) {
823
966
  const delay = Math.min(RETRY_CONFIG.baseDelayMs * 2 ** attempt, RETRY_CONFIG.maxDelayMs);
824
967
  return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
825
968
  }
826
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
969
+ var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
827
970
 
828
971
  class Semaphore {
829
972
  permits;
@@ -888,10 +1031,12 @@ class HarmonyApiClient {
888
1031
  apiKey;
889
1032
  apiUrl;
890
1033
  onUnauthorized;
1034
+ refreshCredential;
891
1035
  constructor(options) {
892
1036
  this.apiKey = options?.apiKey ?? getApiKey();
893
1037
  this.apiUrl = options?.apiUrl ?? getApiUrl();
894
1038
  this.onUnauthorized = options?.onUnauthorized;
1039
+ this.refreshCredential = options?.refreshCredential;
895
1040
  }
896
1041
  getApiUrl() {
897
1042
  return this.apiUrl;
@@ -921,6 +1066,7 @@ class HarmonyApiClient {
921
1066
  async requestWithRetry(method, path, body, options) {
922
1067
  const url = `${this.apiUrl}/v1${path}`;
923
1068
  let lastError = null;
1069
+ let refreshed = false;
924
1070
  const contentType = options?.contentType || "application/json";
925
1071
  const accept = options?.accept || "application/json";
926
1072
  for (let attempt = 0;attempt <= RETRY_CONFIG.maxRetries; attempt++) {
@@ -949,6 +1095,15 @@ class HarmonyApiClient {
949
1095
  if (!response.ok) {
950
1096
  const errorMsg = data?.error || (looksLikeJson ? null : `API error: ${response.status} (non-JSON response)`) || `API error: ${response.status}`;
951
1097
  if (response.status === 401) {
1098
+ if (this.refreshCredential && !refreshed) {
1099
+ refreshed = true;
1100
+ const fresh = await this.refreshCredential();
1101
+ if (fresh) {
1102
+ this.apiKey = fresh;
1103
+ attempt--;
1104
+ continue;
1105
+ }
1106
+ }
952
1107
  this.onUnauthorized?.();
953
1108
  throw new HarmonyUnauthorizedError(errorMsg);
954
1109
  }
@@ -957,7 +1112,7 @@ class HarmonyApiClient {
957
1112
  }
958
1113
  lastError = new Error(errorMsg);
959
1114
  if (attempt < RETRY_CONFIG.maxRetries) {
960
- await sleep(getRetryDelay(attempt));
1115
+ await sleep2(getRetryDelay(attempt));
961
1116
  continue;
962
1117
  }
963
1118
  throw lastError;
@@ -971,7 +1126,7 @@ class HarmonyApiClient {
971
1126
  if (!isRetryableError(error))
972
1127
  throw lastError;
973
1128
  if (attempt < RETRY_CONFIG.maxRetries) {
974
- await sleep(getRetryDelay(attempt));
1129
+ await sleep2(getRetryDelay(attempt));
975
1130
  }
976
1131
  }
977
1132
  }
@@ -980,6 +1135,7 @@ class HarmonyApiClient {
980
1135
  async requestRawWithRetry(method, path, body, options) {
981
1136
  const url = `${this.apiUrl}/v1${path}`;
982
1137
  let lastError = null;
1138
+ let refreshed = false;
983
1139
  const contentType = options?.contentType || "application/json";
984
1140
  const accept = options?.accept || "text/markdown";
985
1141
  for (let attempt = 0;attempt <= RETRY_CONFIG.maxRetries; attempt++) {
@@ -1002,6 +1158,15 @@ class HarmonyApiClient {
1002
1158
  errorMsg = text || `API error: ${response.status}`;
1003
1159
  }
1004
1160
  if (response.status === 401) {
1161
+ if (this.refreshCredential && !refreshed) {
1162
+ refreshed = true;
1163
+ const fresh = await this.refreshCredential();
1164
+ if (fresh) {
1165
+ this.apiKey = fresh;
1166
+ attempt--;
1167
+ continue;
1168
+ }
1169
+ }
1005
1170
  this.onUnauthorized?.();
1006
1171
  throw new HarmonyUnauthorizedError(errorMsg);
1007
1172
  }
@@ -1010,7 +1175,7 @@ class HarmonyApiClient {
1010
1175
  }
1011
1176
  lastError = new Error(errorMsg);
1012
1177
  if (attempt < RETRY_CONFIG.maxRetries) {
1013
- await sleep(getRetryDelay(attempt));
1178
+ await sleep2(getRetryDelay(attempt));
1014
1179
  continue;
1015
1180
  }
1016
1181
  throw lastError;
@@ -1021,7 +1186,7 @@ class HarmonyApiClient {
1021
1186
  if (!isRetryableError(error))
1022
1187
  throw lastError;
1023
1188
  if (attempt < RETRY_CONFIG.maxRetries) {
1024
- await sleep(getRetryDelay(attempt));
1189
+ await sleep2(getRetryDelay(attempt));
1025
1190
  }
1026
1191
  }
1027
1192
  }
@@ -1603,7 +1768,12 @@ async function loadPromptModules() {
1603
1768
  var client = null;
1604
1769
  function getClient() {
1605
1770
  if (!client) {
1606
- client = new HarmonyApiClient;
1771
+ client = new HarmonyApiClient({
1772
+ refreshCredential: async () => {
1773
+ const { refreshOAuthToken: refreshOAuthToken2 } = await Promise.resolve().then(() => (init_oauth_refresh(), exports_oauth_refresh));
1774
+ return refreshOAuthToken2();
1775
+ }
1776
+ });
1607
1777
  }
1608
1778
  return client;
1609
1779
  }