@openchamber/web 1.4.7 → 1.4.9
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/assets/{ToolOutputDialog-oDE1UvDN.js → ToolOutputDialog-B8nV7-ox.js} +2 -2
- package/dist/assets/index-Xx_yi155.css +1 -0
- package/dist/assets/{index-BqCwlsig.js → index-hhW6FfkZ.js} +2 -2
- package/dist/assets/main-YTC5DZsd.js +128 -0
- package/dist/assets/{vendor-.bun-C07YQe9X.js → vendor-.bun-1E5mTZCK.js} +45 -44
- package/dist/assets/{worker-NULm4lOi.js → worker-C7rwD-e5.js} +1 -1
- package/dist/index.html +63 -4
- package/package.json +2 -2
- package/server/index.js +143 -53
- package/server/lib/git-credentials.js +74 -0
- package/server/lib/git-identity-storage.js +2 -0
- package/server/lib/git-service.js +23 -1
- package/dist/assets/index-CFHNKWvn.css +0 -1
- package/dist/assets/main-Bi1ZnDPY.js +0 -128
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
|
-
|
|
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
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
const
|
|
803
|
-
next =
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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;
|
|
@@ -1807,6 +1851,7 @@ function setupProxy(app) {
|
|
|
1807
1851
|
},
|
|
1808
1852
|
onProxyReq: (proxyReq, req, res) => {
|
|
1809
1853
|
console.log(`Proxying ${req.method} ${req.path} to OpenCode`);
|
|
1854
|
+
|
|
1810
1855
|
if (req.headers.accept && req.headers.accept.includes('text/event-stream')) {
|
|
1811
1856
|
console.log(`[SSE] Setting up SSE proxy for ${req.method} ${req.path}`);
|
|
1812
1857
|
proxyReq.setHeader('Accept', 'text/event-stream');
|
|
@@ -1842,11 +1887,11 @@ function startHealthMonitoring() {
|
|
|
1842
1887
|
}
|
|
1843
1888
|
|
|
1844
1889
|
healthCheckInterval = setInterval(async () => {
|
|
1845
|
-
if (!openCodeProcess || isShuttingDown) return;
|
|
1890
|
+
if (!openCodeProcess || isShuttingDown || isRestartingOpenCode) return;
|
|
1846
1891
|
|
|
1847
1892
|
try {
|
|
1848
|
-
|
|
1849
|
-
if (
|
|
1893
|
+
const healthy = await isOpenCodeProcessHealthy();
|
|
1894
|
+
if (!healthy) {
|
|
1850
1895
|
console.log('OpenCode process not running, restarting...');
|
|
1851
1896
|
await restartOpenCode();
|
|
1852
1897
|
}
|
|
@@ -1870,28 +1915,29 @@ async function gracefulShutdown(options = {}) {
|
|
|
1870
1915
|
|
|
1871
1916
|
if (openCodeProcess) {
|
|
1872
1917
|
console.log('Stopping OpenCode process...');
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
}, SHUTDOWN_TIMEOUT);
|
|
1880
|
-
|
|
1881
|
-
openCodeProcess.on('exit', () => {
|
|
1882
|
-
clearTimeout(timeout);
|
|
1883
|
-
resolve();
|
|
1884
|
-
});
|
|
1885
|
-
});
|
|
1918
|
+
try {
|
|
1919
|
+
openCodeProcess.close();
|
|
1920
|
+
} catch (error) {
|
|
1921
|
+
console.warn('Error closing OpenCode process:', error);
|
|
1922
|
+
}
|
|
1923
|
+
openCodeProcess = null;
|
|
1886
1924
|
}
|
|
1887
1925
|
|
|
1888
1926
|
if (server) {
|
|
1889
|
-
await
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1927
|
+
await Promise.race([
|
|
1928
|
+
new Promise((resolve) => {
|
|
1929
|
+
server.close(() => {
|
|
1930
|
+
console.log('HTTP server closed');
|
|
1931
|
+
resolve();
|
|
1932
|
+
});
|
|
1933
|
+
}),
|
|
1934
|
+
new Promise((resolve) => {
|
|
1935
|
+
setTimeout(() => {
|
|
1936
|
+
console.warn('Server close timeout reached, forcing shutdown');
|
|
1937
|
+
resolve();
|
|
1938
|
+
}, SHUTDOWN_TIMEOUT);
|
|
1939
|
+
})
|
|
1940
|
+
]);
|
|
1895
1941
|
}
|
|
1896
1942
|
|
|
1897
1943
|
if (uiAuthController) {
|
|
@@ -1931,7 +1977,7 @@ async function main(options = {}) {
|
|
|
1931
1977
|
status: 'ok',
|
|
1932
1978
|
timestamp: new Date().toISOString(),
|
|
1933
1979
|
openCodePort: openCodePort,
|
|
1934
|
-
openCodeRunning: Boolean(
|
|
1980
|
+
openCodeRunning: Boolean(openCodePort && isOpenCodeReady && !isRestartingOpenCode),
|
|
1935
1981
|
openCodeApiPrefix,
|
|
1936
1982
|
openCodeApiPrefixDetected,
|
|
1937
1983
|
isOpenCodeReady,
|
|
@@ -1952,16 +1998,16 @@ async function main(options = {}) {
|
|
|
1952
1998
|
req.path.startsWith('/api/opencode')
|
|
1953
1999
|
) {
|
|
1954
2000
|
|
|
1955
|
-
express.json()(req, res, next);
|
|
2001
|
+
express.json({ limit: '50mb' })(req, res, next);
|
|
1956
2002
|
} else if (req.path.startsWith('/api')) {
|
|
1957
2003
|
|
|
1958
2004
|
next();
|
|
1959
2005
|
} else {
|
|
1960
2006
|
|
|
1961
|
-
express.json()(req, res, next);
|
|
2007
|
+
express.json({ limit: '50mb' })(req, res, next);
|
|
1962
2008
|
}
|
|
1963
2009
|
});
|
|
1964
|
-
app.use(express.urlencoded({ extended: true }));
|
|
2010
|
+
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
|
|
1965
2011
|
|
|
1966
2012
|
app.use((req, res, next) => {
|
|
1967
2013
|
console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
|
|
@@ -2385,11 +2431,15 @@ async function main(options = {}) {
|
|
|
2385
2431
|
});
|
|
2386
2432
|
|
|
2387
2433
|
app.put('/api/config/settings', async (req, res) => {
|
|
2434
|
+
console.log(`[API:PUT /api/config/settings] Received request`);
|
|
2435
|
+
console.log(`[API:PUT /api/config/settings] Request body:`, JSON.stringify(req.body, null, 2));
|
|
2388
2436
|
try {
|
|
2389
2437
|
const updated = await persistSettings(req.body ?? {});
|
|
2438
|
+
console.log(`[API:PUT /api/config/settings] Success, returning ${updated.projects?.length || 0} projects`);
|
|
2390
2439
|
res.json(updated);
|
|
2391
2440
|
} catch (error) {
|
|
2392
|
-
console.error(
|
|
2441
|
+
console.error(`[API:PUT /api/config/settings] Failed to save settings:`, error);
|
|
2442
|
+
console.error(`[API:PUT /api/config/settings] Error stack:`, error.stack);
|
|
2393
2443
|
res.status(500).json({ error: error instanceof Error ? error.message : 'Failed to save settings' });
|
|
2394
2444
|
}
|
|
2395
2445
|
});
|
|
@@ -3256,6 +3306,17 @@ async function main(options = {}) {
|
|
|
3256
3306
|
}
|
|
3257
3307
|
});
|
|
3258
3308
|
|
|
3309
|
+
app.get('/api/git/discover-credentials', async (req, res) => {
|
|
3310
|
+
try {
|
|
3311
|
+
const { discoverGitCredentials } = await import('./lib/git-credentials.js');
|
|
3312
|
+
const credentials = discoverGitCredentials();
|
|
3313
|
+
res.json(credentials);
|
|
3314
|
+
} catch (error) {
|
|
3315
|
+
console.error('Failed to discover git credentials:', error);
|
|
3316
|
+
res.status(500).json({ error: 'Failed to discover git credentials' });
|
|
3317
|
+
}
|
|
3318
|
+
});
|
|
3319
|
+
|
|
3259
3320
|
app.get('/api/git/check', async (req, res) => {
|
|
3260
3321
|
const { isGitRepository } = await getGitLibraries();
|
|
3261
3322
|
try {
|
|
@@ -3272,6 +3333,23 @@ async function main(options = {}) {
|
|
|
3272
3333
|
}
|
|
3273
3334
|
});
|
|
3274
3335
|
|
|
3336
|
+
app.get('/api/git/remote-url', async (req, res) => {
|
|
3337
|
+
const { getRemoteUrl } = await getGitLibraries();
|
|
3338
|
+
try {
|
|
3339
|
+
const directory = req.query.directory;
|
|
3340
|
+
if (!directory) {
|
|
3341
|
+
return res.status(400).json({ error: 'directory parameter is required' });
|
|
3342
|
+
}
|
|
3343
|
+
const remote = req.query.remote || 'origin';
|
|
3344
|
+
|
|
3345
|
+
const url = await getRemoteUrl(directory, remote);
|
|
3346
|
+
res.json({ url });
|
|
3347
|
+
} catch (error) {
|
|
3348
|
+
console.error('Failed to get remote url:', error);
|
|
3349
|
+
res.status(500).json({ error: 'Failed to get remote url' });
|
|
3350
|
+
}
|
|
3351
|
+
});
|
|
3352
|
+
|
|
3275
3353
|
app.get('/api/git/current-identity', async (req, res) => {
|
|
3276
3354
|
const { getCurrentIdentity } = await getGitLibraries();
|
|
3277
3355
|
try {
|
|
@@ -3456,6 +3534,15 @@ async function main(options = {}) {
|
|
|
3456
3534
|
.join('\n\n');
|
|
3457
3535
|
|
|
3458
3536
|
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}`;
|
|
3537
|
+
|
|
3538
|
+
const settings = await readSettingsFromDiskMigrated();
|
|
3539
|
+
const rawModel = typeof settings.commitMessageModel === 'string' ? settings.commitMessageModel.trim() : '';
|
|
3540
|
+
const model = (() => {
|
|
3541
|
+
if (!rawModel) return 'big-pickle';
|
|
3542
|
+
const parts = rawModel.split('/').filter(Boolean);
|
|
3543
|
+
const candidate = parts.length > 1 ? parts[parts.length - 1] : parts[0];
|
|
3544
|
+
return candidate || 'big-pickle';
|
|
3545
|
+
})();
|
|
3459
3546
|
|
|
3460
3547
|
const completionTimeout = createTimeoutSignal(LONG_REQUEST_TIMEOUT_MS);
|
|
3461
3548
|
let response;
|
|
@@ -3464,7 +3551,7 @@ async function main(options = {}) {
|
|
|
3464
3551
|
method: 'POST',
|
|
3465
3552
|
headers: { 'Content-Type': 'application/json' },
|
|
3466
3553
|
body: JSON.stringify({
|
|
3467
|
-
model
|
|
3554
|
+
model,
|
|
3468
3555
|
messages: [{ role: 'user', content: prompt }],
|
|
3469
3556
|
max_tokens: 3000,
|
|
3470
3557
|
stream: false,
|
|
@@ -4781,9 +4868,12 @@ async function main(options = {}) {
|
|
|
4781
4868
|
});
|
|
4782
4869
|
|
|
4783
4870
|
if (attachSignals && !signalsAttached) {
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4871
|
+
const handleSignal = async () => {
|
|
4872
|
+
await gracefulShutdown();
|
|
4873
|
+
};
|
|
4874
|
+
process.on('SIGTERM', handleSignal);
|
|
4875
|
+
process.on('SIGINT', handleSignal);
|
|
4876
|
+
process.on('SIGQUIT', handleSignal);
|
|
4787
4877
|
signalsAttached = true;
|
|
4788
4878
|
syncToHmrState();
|
|
4789
4879
|
}
|
|
@@ -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
|
-
|
|
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;
|