@openchamber/web 1.4.7 → 1.4.8

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/index.html CHANGED
@@ -15,6 +15,12 @@
15
15
  <link rel="apple-touch-icon" sizes="167x167" href="/apple-touch-icon-167x167.png" />
16
16
  <link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png" />
17
17
 
18
+ <!-- Preload Nerd Fonts for terminal icon display -->
19
+ <link rel="preload" href="https://cdn.jsdelivr.net/gh/mshaugh/nerdfont-webfonts@v3.3.0/build/fonts/JetBrainsMonoNerdFont-Regular.woff2"
20
+ as="font" type="font/woff2" crossorigin="anonymous">
21
+ <link rel="preload" href="https://cdn.jsdelivr.net/gh/mshaugh/nerdfont-webfonts@v3.3.0/build/fonts/FiraCodeNerdFont-Regular.woff2"
22
+ as="font" type="font/woff2" crossorigin="anonymous">
23
+
18
24
  <!-- Web app manifest (data URL to avoid nginx auth issues) -->
19
25
  <script>
20
26
  const baseUrl = location.origin;
@@ -119,8 +125,33 @@
119
125
  <meta name="application-name" content="OpenChamber" />
120
126
  <meta name="apple-mobile-web-app-title" content="OpenChamber" />
121
127
 
122
- <!-- Inline CSS for loading screen (before Tailwind loads) -->
128
+ <!-- Inline CSS for loading screen and Nerd Fonts (before Tailwind loads) -->
123
129
  <style>
130
+ /* Nerd Font @font-face declarations for terminal icon support */
131
+ @font-face {
132
+ font-family: 'JetBrainsMono Nerd Font';
133
+ src:
134
+ local('JetBrainsMono Nerd Font'),
135
+ url('https://cdn.jsdelivr.net/gh/mshaugh/nerdfont-webfonts@v3.3.0/build/fonts/JetBrainsMonoNerdFont-Regular.woff2') format('woff2'),
136
+ url('https://cdn.jsdelivr.net/gh/mshaugh/nerdfont-webfonts@v3.3.0/build/fonts/JetBrainsMonoNerdFont-Regular.woff') format('woff');
137
+ font-weight: normal;
138
+ font-style: normal;
139
+ font-display: swap;
140
+ unicode-range: U+E000-F8FF, U+F0000-FFFFF;
141
+ }
142
+
143
+ @font-face {
144
+ font-family: 'FiraCode Nerd Font';
145
+ src:
146
+ local('FiraCode Nerd Font'),
147
+ url('https://cdn.jsdelivr.net/gh/mshaugh/nerdfont-webfonts@v3.3.0/build/fonts/FiraCodeNerdFont-Regular.woff2') format('woff2'),
148
+ url('https://cdn.jsdelivr.net/gh/mshaugh/nerdfont-webfonts@v3.3.0/build/fonts/FiraCodeNerdFont-Regular.woff') format('woff');
149
+ font-weight: normal;
150
+ font-style: normal;
151
+ font-display: swap;
152
+ unicode-range: U+E000-F8FF, U+F0000-FFFFF;
153
+ }
154
+
124
155
  :root {
125
156
  --splash-background: #151313;
126
157
  --splash-stroke: white;
@@ -160,10 +191,10 @@
160
191
  pointer-events: none;
161
192
  }
162
193
  </style>
163
- <script type="module" crossorigin src="/assets/index-BqCwlsig.js"></script>
164
- <link rel="modulepreload" crossorigin href="/assets/vendor-.bun-C07YQe9X.js">
194
+ <script type="module" crossorigin src="/assets/index-CyM2ZMJa.js"></script>
195
+ <link rel="modulepreload" crossorigin href="/assets/vendor-.bun-94fMDU1C.js">
165
196
  <link rel="stylesheet" crossorigin href="/assets/vendor--Jn2c0Clh.css">
166
- <link rel="stylesheet" crossorigin href="/assets/index-CFHNKWvn.css">
197
+ <link rel="stylesheet" crossorigin href="/assets/index-RdQawb7R.css">
167
198
  </head>
168
199
  <body class="h-full bg-background text-foreground">
169
200
  <div id="root" class="h-full">
@@ -243,6 +274,34 @@
243
274
  }, 10000);
244
275
  </script>
245
276
 
277
+ <!-- CSS Font Loading API for reliable Nerd Font loading -->
278
+ <script>
279
+ (function() {
280
+ const fonts = [
281
+ {
282
+ name: 'JetBrainsMono Nerd Font',
283
+ url: 'https://cdn.jsdelivr.net/gh/mshaugh/nerdfont-webfonts@v3.3.0/build/fonts/JetBrainsMonoNerdFont-Regular.woff2'
284
+ },
285
+ {
286
+ name: 'FiraCode Nerd Font',
287
+ url: 'https://cdn.jsdelivr.net/gh/mshaugh/nerdfont-webfonts@v3.3.0/build/fonts/FiraCodeNerdFont-Regular.woff2'
288
+ }
289
+ ];
290
+
291
+ const fontPromises = fonts.map(font => {
292
+ const fontFace = new FontFace(font.name, `url(${font.url}) format('woff2')`);
293
+ document.fonts.add(fontFace);
294
+ return fontFace.load().catch(err => {
295
+ console.warn(`Failed to load font: ${font.name}`, err);
296
+ });
297
+ });
298
+
299
+ Promise.allSettled(fontPromises).then(() => {
300
+ document.documentElement.classList.add('fonts-loaded');
301
+ });
302
+ })();
303
+ </script>
304
+
246
305
  <!-- Polyfill for process before loading React -->
247
306
  <script>
248
307
  if (typeof process === 'undefined') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openchamber/web",
3
- "version": "1.4.7",
3
+ "version": "1.4.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./server/index.js",
package/server/index.js CHANGED
@@ -39,6 +39,9 @@ const FILE_SEARCH_EXCLUDED_DIRS = new Set([
39
39
  'logs'
40
40
  ]);
41
41
 
42
+ // Lock to prevent race conditions in persistSettings
43
+ let persistSettingsLock = Promise.resolve();
44
+
42
45
  const normalizeDirectoryPath = (value) => {
43
46
  if (typeof value !== 'string') {
44
47
  return value;
@@ -608,6 +611,10 @@ const sanitizeSettingsUpdate = (payload) => {
608
611
  if (typeof candidate.autoCreateWorktree === 'boolean') {
609
612
  result.autoCreateWorktree = candidate.autoCreateWorktree;
610
613
  }
614
+ if (typeof candidate.commitMessageModel === 'string') {
615
+ const trimmed = candidate.commitMessageModel.trim();
616
+ result.commitMessageModel = trimmed.length > 0 ? trimmed : undefined;
617
+ }
611
618
 
612
619
  const skillCatalogs = sanitizeSkillCatalogs(candidate.skillCatalogs);
613
620
  if (skillCatalogs) {
@@ -696,30 +703,39 @@ const formatSettingsResponse = (settings) => {
696
703
  };
697
704
 
698
705
  const validateProjectEntries = async (projects) => {
706
+ console.log(`[validateProjectEntries] Starting validation for ${projects.length} projects`);
707
+
699
708
  if (!Array.isArray(projects)) {
709
+ console.warn(`[validateProjectEntries] Input is not an array, returning empty`);
700
710
  return [];
701
711
  }
702
712
 
703
713
  const results = [];
704
714
  for (const project of projects) {
705
715
  if (!project || typeof project.path !== 'string' || project.path.length === 0) {
716
+ console.error(`[validateProjectEntries] Invalid project entry: missing or empty path`, project);
706
717
  continue;
707
718
  }
708
719
  try {
709
720
  const stats = await fsPromises.stat(project.path);
710
721
  if (!stats.isDirectory()) {
722
+ console.error(`[validateProjectEntries] Project path is not a directory: ${project.path}`);
711
723
  continue;
712
724
  }
713
725
  results.push(project);
714
726
  } catch (error) {
715
727
  const err = error;
728
+ console.error(`[validateProjectEntries] Failed to validate project "${project.path}": ${err.code || err.message || err}`);
716
729
  if (err && typeof err === 'object' && err.code === 'ENOENT') {
730
+ console.log(`[validateProjectEntries] Removing project with ENOENT: ${project.path}`);
717
731
  continue;
718
732
  }
719
- continue;
733
+ console.log(`[validateProjectEntries] Keeping project despite non-ENOENT error: ${project.path}`);
734
+ results.push(project);
720
735
  }
721
736
  }
722
737
 
738
+ console.log(`[validateProjectEntries] Validation complete: ${results.length}/${projects.length} projects valid`);
723
739
  return results;
724
740
  };
725
741
 
@@ -794,27 +810,39 @@ const readSettingsFromDiskMigrated = async () => {
794
810
  };
795
811
 
796
812
  const persistSettings = async (changes) => {
797
- const current = await readSettingsFromDisk();
798
- const sanitized = sanitizeSettingsUpdate(changes);
799
- let next = mergePersistedSettings(current, sanitized);
800
-
801
- if (Array.isArray(next.projects)) {
802
- const validated = await validateProjectEntries(next.projects);
803
- next = { ...next, projects: validated };
804
- }
805
-
806
- if (Array.isArray(next.projects) && next.projects.length > 0) {
807
- const activeId = typeof next.activeProjectId === 'string' ? next.activeProjectId : '';
808
- const active = next.projects.find((project) => project.id === activeId) || null;
809
- if (!active) {
810
- next = { ...next, activeProjectId: next.projects[0].id };
811
- }
812
- } else if (next.activeProjectId) {
813
- next = { ...next, activeProjectId: undefined };
814
- }
815
-
816
- await writeSettingsToDisk(next);
817
- return formatSettingsResponse(next);
813
+ // Serialize concurrent calls using lock
814
+ persistSettingsLock = persistSettingsLock.then(async () => {
815
+ console.log(`[persistSettings] Called with changes:`, JSON.stringify(changes, null, 2));
816
+ const current = await readSettingsFromDisk();
817
+ console.log(`[persistSettings] Current projects count:`, Array.isArray(current.projects) ? current.projects.length : 'N/A');
818
+ const sanitized = sanitizeSettingsUpdate(changes);
819
+ let next = mergePersistedSettings(current, sanitized);
820
+
821
+ if (Array.isArray(next.projects)) {
822
+ console.log(`[persistSettings] Validating ${next.projects.length} projects...`);
823
+ const validated = await validateProjectEntries(next.projects);
824
+ console.log(`[persistSettings] After validation: ${validated.length} projects remain`);
825
+ next = { ...next, projects: validated };
826
+ }
827
+
828
+ if (Array.isArray(next.projects) && next.projects.length > 0) {
829
+ const activeId = typeof next.activeProjectId === 'string' ? next.activeProjectId : '';
830
+ const active = next.projects.find((project) => project.id === activeId) || null;
831
+ if (!active) {
832
+ console.log(`[persistSettings] Active project ID ${activeId} not found, switching to ${next.projects[0].id}`);
833
+ next = { ...next, activeProjectId: next.projects[0].id };
834
+ }
835
+ } else if (next.activeProjectId) {
836
+ console.log(`[persistSettings] No projects found, clearing activeProjectId ${next.activeProjectId}`);
837
+ next = { ...next, activeProjectId: undefined };
838
+ }
839
+
840
+ await writeSettingsToDisk(next);
841
+ console.log(`[persistSettings] Successfully saved ${next.projects?.length || 0} projects to disk`);
842
+ return formatSettingsResponse(next);
843
+ });
844
+
845
+ return persistSettingsLock;
818
846
  };
819
847
 
820
848
  // HMR-persistent state via globalThis
@@ -945,6 +973,22 @@ function setOpenCodePort(port) {
945
973
  lastOpenCodeError = null;
946
974
  }
947
975
 
976
+ async function waitForOpenCodePort(timeoutMs = 15000) {
977
+ if (openCodePort !== null) {
978
+ return openCodePort;
979
+ }
980
+
981
+ const deadline = Date.now() + timeoutMs;
982
+ while (Date.now() < deadline) {
983
+ await new Promise((resolve) => setTimeout(resolve, 50));
984
+ if (openCodePort !== null) {
985
+ return openCodePort;
986
+ }
987
+ }
988
+
989
+ throw new Error('Timed out waiting for OpenCode port');
990
+ }
991
+
948
992
  function getLoginShellPath() {
949
993
  if (process.platform === 'win32') {
950
994
  return null;
@@ -1842,11 +1886,11 @@ function startHealthMonitoring() {
1842
1886
  }
1843
1887
 
1844
1888
  healthCheckInterval = setInterval(async () => {
1845
- if (!openCodeProcess || isShuttingDown) return;
1889
+ if (!openCodeProcess || isShuttingDown || isRestartingOpenCode) return;
1846
1890
 
1847
1891
  try {
1848
-
1849
- if (openCodeProcess.exitCode !== null) {
1892
+ const healthy = await isOpenCodeProcessHealthy();
1893
+ if (!healthy) {
1850
1894
  console.log('OpenCode process not running, restarting...');
1851
1895
  await restartOpenCode();
1852
1896
  }
@@ -1870,28 +1914,29 @@ async function gracefulShutdown(options = {}) {
1870
1914
 
1871
1915
  if (openCodeProcess) {
1872
1916
  console.log('Stopping OpenCode process...');
1873
- openCodeProcess.kill('SIGTERM');
1874
-
1875
- await new Promise((resolve) => {
1876
- const timeout = setTimeout(() => {
1877
- openCodeProcess.kill('SIGKILL');
1878
- resolve();
1879
- }, SHUTDOWN_TIMEOUT);
1880
-
1881
- openCodeProcess.on('exit', () => {
1882
- clearTimeout(timeout);
1883
- resolve();
1884
- });
1885
- });
1917
+ try {
1918
+ openCodeProcess.close();
1919
+ } catch (error) {
1920
+ console.warn('Error closing OpenCode process:', error);
1921
+ }
1922
+ openCodeProcess = null;
1886
1923
  }
1887
1924
 
1888
1925
  if (server) {
1889
- await new Promise((resolve) => {
1890
- server.close(() => {
1891
- console.log('HTTP server closed');
1892
- resolve();
1893
- });
1894
- });
1926
+ await Promise.race([
1927
+ new Promise((resolve) => {
1928
+ server.close(() => {
1929
+ console.log('HTTP server closed');
1930
+ resolve();
1931
+ });
1932
+ }),
1933
+ new Promise((resolve) => {
1934
+ setTimeout(() => {
1935
+ console.warn('Server close timeout reached, forcing shutdown');
1936
+ resolve();
1937
+ }, SHUTDOWN_TIMEOUT);
1938
+ })
1939
+ ]);
1895
1940
  }
1896
1941
 
1897
1942
  if (uiAuthController) {
@@ -1931,7 +1976,7 @@ async function main(options = {}) {
1931
1976
  status: 'ok',
1932
1977
  timestamp: new Date().toISOString(),
1933
1978
  openCodePort: openCodePort,
1934
- openCodeRunning: Boolean(openCodeProcess && openCodeProcess.exitCode === null),
1979
+ openCodeRunning: Boolean(openCodePort && isOpenCodeReady && !isRestartingOpenCode),
1935
1980
  openCodeApiPrefix,
1936
1981
  openCodeApiPrefixDetected,
1937
1982
  isOpenCodeReady,
@@ -2385,11 +2430,15 @@ async function main(options = {}) {
2385
2430
  });
2386
2431
 
2387
2432
  app.put('/api/config/settings', async (req, res) => {
2433
+ console.log(`[API:PUT /api/config/settings] Received request`);
2434
+ console.log(`[API:PUT /api/config/settings] Request body:`, JSON.stringify(req.body, null, 2));
2388
2435
  try {
2389
2436
  const updated = await persistSettings(req.body ?? {});
2437
+ console.log(`[API:PUT /api/config/settings] Success, returning ${updated.projects?.length || 0} projects`);
2390
2438
  res.json(updated);
2391
2439
  } catch (error) {
2392
- console.error('Failed to save settings:', error);
2440
+ console.error(`[API:PUT /api/config/settings] Failed to save settings:`, error);
2441
+ console.error(`[API:PUT /api/config/settings] Error stack:`, error.stack);
2393
2442
  res.status(500).json({ error: error instanceof Error ? error.message : 'Failed to save settings' });
2394
2443
  }
2395
2444
  });
@@ -3256,6 +3305,17 @@ async function main(options = {}) {
3256
3305
  }
3257
3306
  });
3258
3307
 
3308
+ app.get('/api/git/discover-credentials', async (req, res) => {
3309
+ try {
3310
+ const { discoverGitCredentials } = await import('./lib/git-credentials.js');
3311
+ const credentials = discoverGitCredentials();
3312
+ res.json(credentials);
3313
+ } catch (error) {
3314
+ console.error('Failed to discover git credentials:', error);
3315
+ res.status(500).json({ error: 'Failed to discover git credentials' });
3316
+ }
3317
+ });
3318
+
3259
3319
  app.get('/api/git/check', async (req, res) => {
3260
3320
  const { isGitRepository } = await getGitLibraries();
3261
3321
  try {
@@ -3272,6 +3332,23 @@ async function main(options = {}) {
3272
3332
  }
3273
3333
  });
3274
3334
 
3335
+ app.get('/api/git/remote-url', async (req, res) => {
3336
+ const { getRemoteUrl } = await getGitLibraries();
3337
+ try {
3338
+ const directory = req.query.directory;
3339
+ if (!directory) {
3340
+ return res.status(400).json({ error: 'directory parameter is required' });
3341
+ }
3342
+ const remote = req.query.remote || 'origin';
3343
+
3344
+ const url = await getRemoteUrl(directory, remote);
3345
+ res.json({ url });
3346
+ } catch (error) {
3347
+ console.error('Failed to get remote url:', error);
3348
+ res.status(500).json({ error: 'Failed to get remote url' });
3349
+ }
3350
+ });
3351
+
3275
3352
  app.get('/api/git/current-identity', async (req, res) => {
3276
3353
  const { getCurrentIdentity } = await getGitLibraries();
3277
3354
  try {
@@ -3456,6 +3533,15 @@ async function main(options = {}) {
3456
3533
  .join('\n\n');
3457
3534
 
3458
3535
  const prompt = `You are drafting git commit notes for this codebase. Respond in JSON of the shape {"subject": string, "highlights": string[]} (ONLY the JSON in response, no markdown wrappers or anything except JSON) with these rules:\n- subject follows our convention: type[optional-scope]: summary (examples: "feat: add diff virtualization", "fix(chat): restore enter key handling")\n- allowed types: feat, fix, chore, style, refactor, perf, docs, test, build, ci (choose the best match or fallback to chore)\n- summary must be imperative, concise, <= 70 characters, no trailing punctuation\n- scope is optional; include only when obvious from filenames/folders; do not invent scopes\n- focus on the most impactful user-facing change; if multiple capabilities ship together, align the subject with the dominant theme and use highlights to cover the other major outcomes\n- highlights array should contain 2-3 plain sentences (<= 90 chars each) that describe distinct features or UI changes users will notice (e.g. "Add per-file revert action in Changes list"). Avoid subjective benefit statements, marketing tone, repeating the subject, or referencing helper function names. Highlight additions such as new controls/buttons, new actions (e.g. revert), or stored state changes explicitly. Skip highlights if fewer than two meaningful points exist.\n- text must be plain (no markdown bullets); each highlight should start with an uppercase verb\n\nDiff summary:\n${diffSummaries}`;
3536
+
3537
+ const settings = await readSettingsFromDiskMigrated();
3538
+ const rawModel = typeof settings.commitMessageModel === 'string' ? settings.commitMessageModel.trim() : '';
3539
+ const model = (() => {
3540
+ if (!rawModel) return 'big-pickle';
3541
+ const parts = rawModel.split('/').filter(Boolean);
3542
+ const candidate = parts.length > 1 ? parts[parts.length - 1] : parts[0];
3543
+ return candidate || 'big-pickle';
3544
+ })();
3459
3545
 
3460
3546
  const completionTimeout = createTimeoutSignal(LONG_REQUEST_TIMEOUT_MS);
3461
3547
  let response;
@@ -3464,7 +3550,7 @@ async function main(options = {}) {
3464
3550
  method: 'POST',
3465
3551
  headers: { 'Content-Type': 'application/json' },
3466
3552
  body: JSON.stringify({
3467
- model: 'big-pickle',
3553
+ model,
3468
3554
  messages: [{ role: 'user', content: prompt }],
3469
3555
  max_tokens: 3000,
3470
3556
  stream: false,
@@ -4781,9 +4867,12 @@ async function main(options = {}) {
4781
4867
  });
4782
4868
 
4783
4869
  if (attachSignals && !signalsAttached) {
4784
- process.on('SIGTERM', gracefulShutdown);
4785
- process.on('SIGINT', gracefulShutdown);
4786
- process.on('SIGQUIT', gracefulShutdown);
4870
+ const handleSignal = async () => {
4871
+ await gracefulShutdown();
4872
+ };
4873
+ process.on('SIGTERM', handleSignal);
4874
+ process.on('SIGINT', handleSignal);
4875
+ process.on('SIGQUIT', handleSignal);
4787
4876
  signalsAttached = true;
4788
4877
  syncToHmrState();
4789
4878
  }
@@ -0,0 +1,74 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+
5
+ const GIT_CREDENTIALS_PATH = path.join(os.homedir(), '.git-credentials');
6
+
7
+ export function discoverGitCredentials() {
8
+ const credentials = [];
9
+
10
+ if (!fs.existsSync(GIT_CREDENTIALS_PATH)) {
11
+ return credentials;
12
+ }
13
+
14
+ try {
15
+ const content = fs.readFileSync(GIT_CREDENTIALS_PATH, 'utf8');
16
+ const lines = content.split('\n').filter(line => line.trim());
17
+
18
+ for (const line of lines) {
19
+ try {
20
+ const url = new URL(line.trim());
21
+ const hostname = url.hostname;
22
+ const pathname = url.pathname && url.pathname !== '/' ? url.pathname : '';
23
+ const host = hostname + pathname;
24
+ const username = url.username || '';
25
+
26
+ if (host && username) {
27
+ const exists = credentials.some(c => c.host === host && c.username === username);
28
+ if (!exists) {
29
+ credentials.push({ host, username });
30
+ }
31
+ }
32
+ } catch {
33
+ continue;
34
+ }
35
+ }
36
+ } catch (error) {
37
+ console.error('Failed to read .git-credentials:', error);
38
+ }
39
+
40
+ return credentials;
41
+ }
42
+
43
+ export function getCredentialForHost(host) {
44
+ if (!fs.existsSync(GIT_CREDENTIALS_PATH)) {
45
+ return null;
46
+ }
47
+
48
+ try {
49
+ const content = fs.readFileSync(GIT_CREDENTIALS_PATH, 'utf8');
50
+ const lines = content.split('\n').filter(line => line.trim());
51
+
52
+ for (const line of lines) {
53
+ try {
54
+ const url = new URL(line.trim());
55
+ const hostname = url.hostname;
56
+ const pathname = url.pathname && url.pathname !== '/' ? url.pathname : '';
57
+ const credHost = hostname + pathname;
58
+
59
+ if (credHost === host) {
60
+ return {
61
+ username: url.username || '',
62
+ token: url.password || ''
63
+ };
64
+ }
65
+ } catch {
66
+ continue;
67
+ }
68
+ }
69
+ } catch (error) {
70
+ console.error('Failed to read .git-credentials for host lookup:', error);
71
+ }
72
+
73
+ return null;
74
+ }
@@ -66,7 +66,9 @@ export function createProfile(profileData) {
66
66
  name: profileData.name || profileData.userName,
67
67
  userName: profileData.userName,
68
68
  userEmail: profileData.userEmail,
69
+ authType: profileData.authType || 'ssh',
69
70
  sshKey: profileData.sshKey || null,
71
+ host: profileData.host || null,
70
72
  color: profileData.color || 'keyword',
71
73
  icon: profileData.icon || 'branch'
72
74
  };
@@ -117,6 +117,17 @@ export async function getGlobalIdentity() {
117
117
  }
118
118
  }
119
119
 
120
+ export async function getRemoteUrl(directory, remoteName = 'origin') {
121
+ const git = simpleGit(normalizeDirectoryPath(directory));
122
+
123
+ try {
124
+ const url = await git.remote(['get-url', remoteName]);
125
+ return url?.trim() || null;
126
+ } catch {
127
+ return null;
128
+ }
129
+ }
130
+
120
131
  export async function getCurrentIdentity(directory) {
121
132
  const git = simpleGit(normalizeDirectoryPath(directory));
122
133
 
@@ -157,13 +168,24 @@ export async function setLocalIdentity(directory, profile) {
157
168
  await git.addConfig('user.name', profile.userName, false, 'local');
158
169
  await git.addConfig('user.email', profile.userEmail, false, 'local');
159
170
 
160
- if (profile.sshKey) {
171
+ const authType = profile.authType || 'ssh';
172
+
173
+ if (authType === 'ssh' && profile.sshKey) {
161
174
  await git.addConfig(
162
175
  'core.sshCommand',
163
176
  `ssh -i ${profile.sshKey}`,
164
177
  false,
165
178
  'local'
166
179
  );
180
+ await git.raw(['config', '--local', '--unset', 'credential.helper']).catch(() => {});
181
+ } else if (authType === 'token' && profile.host) {
182
+ await git.addConfig(
183
+ 'credential.helper',
184
+ 'store',
185
+ false,
186
+ 'local'
187
+ );
188
+ await git.raw(['config', '--local', '--unset', 'core.sshCommand']).catch(() => {});
167
189
  }
168
190
 
169
191
  return true;