fix: treat gh CLI token as GitHub account
This commit is contained in:
@@ -187,11 +187,14 @@ const DesktopGitHubControl = React.memo(function DesktopGitHubControl({
|
|||||||
{githubAccounts.map((account) => {
|
{githubAccounts.map((account) => {
|
||||||
const accountUser = account.user;
|
const accountUser = account.user;
|
||||||
const isCurrent = Boolean(account.current);
|
const isCurrent = Boolean(account.current);
|
||||||
|
const sourceLabel = account.source === 'gh-cli'
|
||||||
|
? t('header.github.accountSource.cli')
|
||||||
|
: t('header.github.accountSource.oauth');
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={account.id}
|
key={account.id}
|
||||||
className="gap-2"
|
className="gap-2"
|
||||||
disabled={isCurrent || isSwitchingGitHubAccount}
|
disabled={isSwitchingGitHubAccount}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
if (!isCurrent) {
|
if (!isCurrent) {
|
||||||
void handleGitHubAccountSwitch(account.id);
|
void handleGitHubAccountSwitch(account.id);
|
||||||
@@ -216,8 +219,10 @@ const DesktopGitHubControl = React.memo(function DesktopGitHubControl({
|
|||||||
{accountUser?.name?.trim() || accountUser?.login || 'GitHub'}
|
{accountUser?.name?.trim() || accountUser?.login || 'GitHub'}
|
||||||
</span>
|
</span>
|
||||||
{accountUser?.login ? (
|
{accountUser?.login ? (
|
||||||
<span className="truncate typography-micro font-mono text-muted-foreground">
|
<span className="truncate typography-micro text-muted-foreground">
|
||||||
{accountUser.login}
|
<span className="font-mono">{accountUser.login}</span>
|
||||||
|
<span className="mx-1 opacity-50">·</span>
|
||||||
|
<span>{sourceLabel}</span>
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -263,7 +263,11 @@ export const GitHubSettings: React.FC = () => {
|
|||||||
const connected = Boolean(status?.connected);
|
const connected = Boolean(status?.connected);
|
||||||
const user = status?.user;
|
const user = status?.user;
|
||||||
const accounts = status?.accounts ?? [];
|
const accounts = status?.accounts ?? [];
|
||||||
|
const otherAccounts = accounts.filter((account) => !account.current);
|
||||||
const ghCli = status?.ghCli ?? null;
|
const ghCli = status?.ghCli ?? null;
|
||||||
|
const activeAccountSourceLabel = ghCli?.active
|
||||||
|
? t('settings.github.page.accountSource.cli')
|
||||||
|
: t('settings.github.page.accountSource.oauth');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
@@ -306,6 +310,8 @@ export const GitHubSettings: React.FC = () => {
|
|||||||
<span className="font-mono">{user?.login || t('settings.github.page.label.unknownUser')}</span>
|
<span className="font-mono">{user?.login || t('settings.github.page.label.unknownUser')}</span>
|
||||||
{user?.email && <span className="opacity-50">•</span>}
|
{user?.email && <span className="opacity-50">•</span>}
|
||||||
{user?.email && <span>{user.email}</span>}
|
{user?.email && <span>{user.email}</span>}
|
||||||
|
<span className="opacity-50">•</span>
|
||||||
|
<span>{activeAccountSourceLabel}</span>
|
||||||
</div>
|
</div>
|
||||||
{status?.scope && (
|
{status?.scope && (
|
||||||
<div className="typography-micro text-muted-foreground/70 mt-0.5">
|
<div className="typography-micro text-muted-foreground/70 mt-0.5">
|
||||||
@@ -341,15 +347,17 @@ export const GitHubSettings: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{accounts.length > 1 && (
|
{otherAccounts.length > 0 && (
|
||||||
<div className="mt-2 border-t border-[var(--surface-subtle)] pt-2 px-2 pb-1">
|
<div className="mt-2 border-t border-[var(--surface-subtle)] pt-2 px-2 pb-1">
|
||||||
<div className="typography-micro text-muted-foreground mb-2 px-1">
|
<div className="typography-micro text-muted-foreground mb-2 px-1">
|
||||||
{t('settings.github.page.label.otherAccounts')}
|
{t('settings.github.page.label.otherAccounts')}
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{accounts.map((account) => {
|
{otherAccounts.map((account) => {
|
||||||
const accountUser = account.user;
|
const accountUser = account.user;
|
||||||
const isCurrent = Boolean(account.current);
|
const sourceLabel = account.source === 'gh-cli'
|
||||||
|
? t('settings.github.page.accountSource.cli')
|
||||||
|
: t('settings.github.page.accountSource.oauth');
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={account.id}
|
key={account.id}
|
||||||
@@ -374,25 +382,21 @@ export const GitHubSettings: React.FC = () => {
|
|||||||
{accountUser?.name?.trim() || accountUser?.login || 'GitHub'}
|
{accountUser?.name?.trim() || accountUser?.login || 'GitHub'}
|
||||||
</span>
|
</span>
|
||||||
{accountUser?.login && (
|
{accountUser?.login && (
|
||||||
<span className="typography-micro text-muted-foreground truncate font-mono">
|
<span className="typography-micro text-muted-foreground truncate">
|
||||||
{accountUser.login}
|
<span className="font-mono">{accountUser.login}</span>
|
||||||
|
<span className="mx-1 opacity-50">·</span>
|
||||||
|
<span>{sourceLabel}</span>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isCurrent ? (
|
<Button size="sm"
|
||||||
<span className="typography-micro text-[var(--primary-base)] bg-[var(--primary-base)]/10 px-1.5 py-0.5 rounded">
|
variant="ghost"
|
||||||
{t('settings.github.page.status.active')}
|
onClick={() => activateAccount(account.id)}
|
||||||
</span>
|
disabled={isBusy}
|
||||||
) : (
|
>
|
||||||
<Button size="sm"
|
{t('settings.github.page.actions.switchTo')}
|
||||||
variant="ghost"
|
</Button>
|
||||||
onClick={() => activateAccount(account.id)}
|
|
||||||
disabled={isBusy}
|
|
||||||
>
|
|
||||||
{t('settings.github.page.actions.switchTo')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -449,7 +453,7 @@ export const GitHubSettings: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{ghCli?.available && !ghCli?.active && (
|
{ghCli?.available && !ghCli?.active && (!ghCli.user || ghCli.disabled) && (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<h3 className="typography-ui-header font-semibold text-foreground mb-3 px-1">
|
<h3 className="typography-ui-header font-semibold text-foreground mb-3 px-1">
|
||||||
{t('settings.github.page.ghCli.title')}
|
{t('settings.github.page.ghCli.title')}
|
||||||
|
|||||||
@@ -1052,6 +1052,7 @@ export type GitHubAuthAccount = {
|
|||||||
user: GitHubUserSummary;
|
user: GitHubUserSummary;
|
||||||
scope?: string;
|
scope?: string;
|
||||||
current?: boolean;
|
current?: boolean;
|
||||||
|
source?: 'oauth' | 'gh-cli';
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GitHubDeviceFlowStart = {
|
export type GitHubDeviceFlowStart = {
|
||||||
|
|||||||
@@ -1390,6 +1390,8 @@ export const settingsDict = {
|
|||||||
'settings.github.page.label.unknownUser': 'unknown',
|
'settings.github.page.label.unknownUser': 'unknown',
|
||||||
'settings.github.page.label.scopes': 'Scopes: {value}',
|
'settings.github.page.label.scopes': 'Scopes: {value}',
|
||||||
'settings.github.page.label.otherAccounts': 'Other Accounts',
|
'settings.github.page.label.otherAccounts': 'Other Accounts',
|
||||||
|
'settings.github.page.accountSource.oauth': 'OAuth',
|
||||||
|
'settings.github.page.accountSource.cli': 'CLI',
|
||||||
'settings.github.page.actions.disconnect': 'Disconnect',
|
'settings.github.page.actions.disconnect': 'Disconnect',
|
||||||
'settings.github.page.actions.connect': 'Connect GitHub',
|
'settings.github.page.actions.connect': 'Connect GitHub',
|
||||||
'settings.github.page.actions.switchTo': 'Switch to',
|
'settings.github.page.actions.switchTo': 'Switch to',
|
||||||
|
|||||||
@@ -1273,6 +1273,8 @@ export const dict = {
|
|||||||
'header.github.avatarWithLogin': '{login} avatar',
|
'header.github.avatarWithLogin': '{login} avatar',
|
||||||
'header.github.avatar': 'GitHub avatar',
|
'header.github.avatar': 'GitHub avatar',
|
||||||
'header.github.accountsTitle': 'GitHub Accounts',
|
'header.github.accountsTitle': 'GitHub Accounts',
|
||||||
|
'header.github.accountSource.oauth': 'OAuth',
|
||||||
|
'header.github.accountSource.cli': 'CLI',
|
||||||
'header.services.openWithCurrent': 'Open instance, usage and MCP (current: {current})',
|
'header.services.openWithCurrent': 'Open instance, usage and MCP (current: {current})',
|
||||||
'header.services.open': 'Open services, usage and MCP',
|
'header.services.open': 'Open services, usage and MCP',
|
||||||
'header.services.tooltip.currentInstanceWithShortcuts': 'Current instance: {current} ({toggle}; next tab {nextTab})',
|
'header.services.tooltip.currentInstanceWithShortcuts': 'Current instance: {current} ({toggle}; next tab {nextTab})',
|
||||||
|
|||||||
@@ -1357,6 +1357,8 @@ export const settingsDict = {
|
|||||||
"settings.github.page.label.unknownUser": "desconocido",
|
"settings.github.page.label.unknownUser": "desconocido",
|
||||||
"settings.github.page.label.scopes": "Alcances: {value}",
|
"settings.github.page.label.scopes": "Alcances: {value}",
|
||||||
"settings.github.page.label.otherAccounts": "Cuentas adicionales",
|
"settings.github.page.label.otherAccounts": "Cuentas adicionales",
|
||||||
|
"settings.github.page.accountSource.oauth": "OAuth",
|
||||||
|
"settings.github.page.accountSource.cli": "CLI",
|
||||||
"settings.github.page.actions.disconnect": "Desconectar",
|
"settings.github.page.actions.disconnect": "Desconectar",
|
||||||
"settings.github.page.actions.connect": "Conectar GitHub",
|
"settings.github.page.actions.connect": "Conectar GitHub",
|
||||||
"settings.github.page.actions.switchTo": "Cambiar a",
|
"settings.github.page.actions.switchTo": "Cambiar a",
|
||||||
|
|||||||
@@ -1239,6 +1239,8 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"header.github.avatarWithLogin": "Avatar de {login}",
|
"header.github.avatarWithLogin": "Avatar de {login}",
|
||||||
"header.github.avatar": "Avatar de GitHub",
|
"header.github.avatar": "Avatar de GitHub",
|
||||||
"header.github.accountsTitle": "Cuentas de GitHub",
|
"header.github.accountsTitle": "Cuentas de GitHub",
|
||||||
|
"header.github.accountSource.oauth": "OAuth",
|
||||||
|
"header.github.accountSource.cli": "CLI",
|
||||||
"header.services.openWithCurrent": "Abrir instancia, uso y MCP (actual: {current})",
|
"header.services.openWithCurrent": "Abrir instancia, uso y MCP (actual: {current})",
|
||||||
"header.services.open": "Abrir servicios, uso y MCP",
|
"header.services.open": "Abrir servicios, uso y MCP",
|
||||||
"header.services.tooltip.currentInstanceWithShortcuts": "Instancia actual: {current} ({toggle}; siguiente pestaña {nextTab})",
|
"header.services.tooltip.currentInstanceWithShortcuts": "Instancia actual: {current} ({toggle}; siguiente pestaña {nextTab})",
|
||||||
|
|||||||
@@ -1346,6 +1346,8 @@ export const settingsDict = {
|
|||||||
'settings.github.page.label.unknownUser': 'inconnu',
|
'settings.github.page.label.unknownUser': 'inconnu',
|
||||||
'settings.github.page.label.scopes': 'Portées : {value}',
|
'settings.github.page.label.scopes': 'Portées : {value}',
|
||||||
'settings.github.page.label.otherAccounts': 'Autres comptes',
|
'settings.github.page.label.otherAccounts': 'Autres comptes',
|
||||||
|
'settings.github.page.accountSource.oauth': 'OAuth',
|
||||||
|
'settings.github.page.accountSource.cli': 'CLI',
|
||||||
'settings.github.page.actions.disconnect': 'Déconnecter',
|
'settings.github.page.actions.disconnect': 'Déconnecter',
|
||||||
'settings.github.page.actions.connect': 'Connectez GitHub',
|
'settings.github.page.actions.connect': 'Connectez GitHub',
|
||||||
'settings.github.page.actions.switchTo': 'Passer à',
|
'settings.github.page.actions.switchTo': 'Passer à',
|
||||||
|
|||||||
@@ -1145,6 +1145,8 @@ export const dict = {
|
|||||||
'header.github.avatarWithLogin': 'Avatar {login}',
|
'header.github.avatarWithLogin': 'Avatar {login}',
|
||||||
'header.github.avatar': 'Avatar GitHub',
|
'header.github.avatar': 'Avatar GitHub',
|
||||||
'header.github.accountsTitle': 'Comptes GitHub',
|
'header.github.accountsTitle': 'Comptes GitHub',
|
||||||
|
'header.github.accountSource.oauth': 'OAuth',
|
||||||
|
'header.github.accountSource.cli': 'CLI',
|
||||||
'header.services.openWithCurrent': 'Instance ouverte, utilisation et MCP (actuel : {current})',
|
'header.services.openWithCurrent': 'Instance ouverte, utilisation et MCP (actuel : {current})',
|
||||||
'header.services.open': 'Services ouverts, utilisation et MCP',
|
'header.services.open': 'Services ouverts, utilisation et MCP',
|
||||||
'header.services.tooltip.currentInstanceWithShortcuts': 'Instance actuelle : {current} ({toggle} ; onglet suivant {nextTab})',
|
'header.services.tooltip.currentInstanceWithShortcuts': 'Instance actuelle : {current} ({toggle} ; onglet suivant {nextTab})',
|
||||||
|
|||||||
@@ -1357,6 +1357,8 @@ export const settingsDict = {
|
|||||||
'settings.github.page.label.unknownUser': '알 수 없음',
|
'settings.github.page.label.unknownUser': '알 수 없음',
|
||||||
'settings.github.page.label.scopes': 'Scopes: {value}',
|
'settings.github.page.label.scopes': 'Scopes: {value}',
|
||||||
'settings.github.page.label.otherAccounts': '다른 계정',
|
'settings.github.page.label.otherAccounts': '다른 계정',
|
||||||
|
'settings.github.page.accountSource.oauth': 'OAuth',
|
||||||
|
'settings.github.page.accountSource.cli': 'CLI',
|
||||||
'settings.github.page.actions.disconnect': '연결 해제',
|
'settings.github.page.actions.disconnect': '연결 해제',
|
||||||
'settings.github.page.actions.connect': 'GitHub 연결',
|
'settings.github.page.actions.connect': 'GitHub 연결',
|
||||||
'settings.github.page.actions.switchTo': '전환',
|
'settings.github.page.actions.switchTo': '전환',
|
||||||
|
|||||||
@@ -1276,6 +1276,8 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'header.github.avatarWithLogin': '{login} 아바타',
|
'header.github.avatarWithLogin': '{login} 아바타',
|
||||||
'header.github.avatar': 'GitHub 아바타',
|
'header.github.avatar': 'GitHub 아바타',
|
||||||
'header.github.accountsTitle': 'GitHub 계정',
|
'header.github.accountsTitle': 'GitHub 계정',
|
||||||
|
'header.github.accountSource.oauth': 'OAuth',
|
||||||
|
'header.github.accountSource.cli': 'CLI',
|
||||||
'header.services.openWithCurrent': '인스턴스, 사용량, MCP 열기(현재: {current})',
|
'header.services.openWithCurrent': '인스턴스, 사용량, MCP 열기(현재: {current})',
|
||||||
'header.services.open': '서비스, 사용량, MCP 열기',
|
'header.services.open': '서비스, 사용량, MCP 열기',
|
||||||
'header.services.tooltip.currentInstanceWithShortcuts': '현재 인스턴스: {current} ({toggle}; 다음 탭 {nextTab})',
|
'header.services.tooltip.currentInstanceWithShortcuts': '현재 인스턴스: {current} ({toggle}; 다음 탭 {nextTab})',
|
||||||
|
|||||||
@@ -235,6 +235,8 @@ export const settingsDict = {
|
|||||||
'settings.github.page.flow.title': 'Autoryzuj OpenChamber',
|
'settings.github.page.flow.title': 'Autoryzuj OpenChamber',
|
||||||
'settings.github.page.flow.waiting': 'Oczekiwanie na zatwierdzenie... (automatyczne odświeżanie)',
|
'settings.github.page.flow.waiting': 'Oczekiwanie na zatwierdzenie... (automatyczne odświeżanie)',
|
||||||
'settings.github.page.label.otherAccounts': 'Inne konta',
|
'settings.github.page.label.otherAccounts': 'Inne konta',
|
||||||
|
'settings.github.page.accountSource.oauth': 'OAuth',
|
||||||
|
'settings.github.page.accountSource.cli': 'CLI',
|
||||||
'settings.github.page.label.scopes': 'Zakresy: {value}',
|
'settings.github.page.label.scopes': 'Zakresy: {value}',
|
||||||
'settings.github.page.label.unknownUser': 'nieznany',
|
'settings.github.page.label.unknownUser': 'nieznany',
|
||||||
'settings.github.page.status.active': 'Aktywne',
|
'settings.github.page.status.active': 'Aktywne',
|
||||||
|
|||||||
@@ -1960,6 +1960,8 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'header.actions.toggleTerminalPanelAria': 'Przełącz panel terminala',
|
'header.actions.toggleTerminalPanelAria': 'Przełącz panel terminala',
|
||||||
'header.changes.availableAria': 'Dostępne zmiany',
|
'header.changes.availableAria': 'Dostępne zmiany',
|
||||||
'header.github.accountsTitle': 'Konta GitHub',
|
'header.github.accountsTitle': 'Konta GitHub',
|
||||||
|
'header.github.accountSource.oauth': 'OAuth',
|
||||||
|
'header.github.accountSource.cli': 'CLI',
|
||||||
'header.github.avatar': 'Awatar GitHub',
|
'header.github.avatar': 'Awatar GitHub',
|
||||||
'header.github.avatarWithLogin': 'Awatar {login}',
|
'header.github.avatarWithLogin': 'Awatar {login}',
|
||||||
'header.github.connected': 'GitHub połączony',
|
'header.github.connected': 'GitHub połączony',
|
||||||
|
|||||||
@@ -1357,6 +1357,8 @@ export const settingsDict = {
|
|||||||
"settings.github.page.label.unknownUser": "desconhecido",
|
"settings.github.page.label.unknownUser": "desconhecido",
|
||||||
"settings.github.page.label.scopes": "Alcances: {value}",
|
"settings.github.page.label.scopes": "Alcances: {value}",
|
||||||
"settings.github.page.label.otherAccounts": "Contas adicionales",
|
"settings.github.page.label.otherAccounts": "Contas adicionales",
|
||||||
|
"settings.github.page.accountSource.oauth": "OAuth",
|
||||||
|
"settings.github.page.accountSource.cli": "CLI",
|
||||||
"settings.github.page.actions.disconnect": "Desconectar",
|
"settings.github.page.actions.disconnect": "Desconectar",
|
||||||
"settings.github.page.actions.connect": "Conectar GitHub",
|
"settings.github.page.actions.connect": "Conectar GitHub",
|
||||||
"settings.github.page.actions.switchTo": "Alternar para",
|
"settings.github.page.actions.switchTo": "Alternar para",
|
||||||
|
|||||||
@@ -1239,6 +1239,8 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"header.github.avatarWithLogin": "Avatar de {login}",
|
"header.github.avatarWithLogin": "Avatar de {login}",
|
||||||
"header.github.avatar": "Avatar de GitHub",
|
"header.github.avatar": "Avatar de GitHub",
|
||||||
"header.github.accountsTitle": "Contas de GitHub",
|
"header.github.accountsTitle": "Contas de GitHub",
|
||||||
|
"header.github.accountSource.oauth": "OAuth",
|
||||||
|
"header.github.accountSource.cli": "CLI",
|
||||||
"header.services.openWithCurrent": "Abrir instância, uso e MCP (atual: {current})",
|
"header.services.openWithCurrent": "Abrir instância, uso e MCP (atual: {current})",
|
||||||
"header.services.open": "Abrir serviços, uso e MCP",
|
"header.services.open": "Abrir serviços, uso e MCP",
|
||||||
"header.services.tooltip.currentInstanceWithShortcuts": "Instância atual: {current} ({toggle}; próxima aba {nextTab})",
|
"header.services.tooltip.currentInstanceWithShortcuts": "Instância atual: {current} ({toggle}; próxima aba {nextTab})",
|
||||||
|
|||||||
@@ -1357,6 +1357,8 @@ export const settingsDict = {
|
|||||||
"settings.github.page.label.unknownUser": "невідомий",
|
"settings.github.page.label.unknownUser": "невідомий",
|
||||||
"settings.github.page.label.scopes": "Області застосування: {value}",
|
"settings.github.page.label.scopes": "Області застосування: {value}",
|
||||||
"settings.github.page.label.otherAccounts": "Інші облікові записи",
|
"settings.github.page.label.otherAccounts": "Інші облікові записи",
|
||||||
|
"settings.github.page.accountSource.oauth": "OAuth",
|
||||||
|
"settings.github.page.accountSource.cli": "CLI",
|
||||||
"settings.github.page.actions.disconnect": "Відключити",
|
"settings.github.page.actions.disconnect": "Відключити",
|
||||||
"settings.github.page.actions.connect": "Підключити GitHub",
|
"settings.github.page.actions.connect": "Підключити GitHub",
|
||||||
"settings.github.page.actions.switchTo": "Перемкнутися на",
|
"settings.github.page.actions.switchTo": "Перемкнутися на",
|
||||||
|
|||||||
@@ -1239,6 +1239,8 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"header.github.avatarWithLogin": "Аватар {login}",
|
"header.github.avatarWithLogin": "Аватар {login}",
|
||||||
"header.github.avatar": "Аватар GitHub",
|
"header.github.avatar": "Аватар GitHub",
|
||||||
"header.github.accountsTitle": "Облікові записи GitHub",
|
"header.github.accountsTitle": "Облікові записи GitHub",
|
||||||
|
"header.github.accountSource.oauth": "OAuth",
|
||||||
|
"header.github.accountSource.cli": "CLI",
|
||||||
"header.services.openWithCurrent": "Відкрити інстанс, використання та MCP (поточний: {current})",
|
"header.services.openWithCurrent": "Відкрити інстанс, використання та MCP (поточний: {current})",
|
||||||
"header.services.open": "Відкрити сервіси, використання та MCP",
|
"header.services.open": "Відкрити сервіси, використання та MCP",
|
||||||
"header.services.tooltip.currentInstanceWithShortcuts": "Поточний інстанс: {current} ({toggle}; наступна вкладка {nextTab})",
|
"header.services.tooltip.currentInstanceWithShortcuts": "Поточний інстанс: {current} ({toggle}; наступна вкладка {nextTab})",
|
||||||
|
|||||||
@@ -1357,6 +1357,8 @@ export const settingsDict = {
|
|||||||
'settings.github.page.label.unknownUser': '未知',
|
'settings.github.page.label.unknownUser': '未知',
|
||||||
'settings.github.page.label.scopes': '作用域:{value}',
|
'settings.github.page.label.scopes': '作用域:{value}',
|
||||||
'settings.github.page.label.otherAccounts': '其他账号',
|
'settings.github.page.label.otherAccounts': '其他账号',
|
||||||
|
'settings.github.page.accountSource.oauth': 'OAuth',
|
||||||
|
'settings.github.page.accountSource.cli': 'CLI',
|
||||||
'settings.github.page.actions.disconnect': '断开连接',
|
'settings.github.page.actions.disconnect': '断开连接',
|
||||||
'settings.github.page.actions.connect': '连接 GitHub',
|
'settings.github.page.actions.connect': '连接 GitHub',
|
||||||
'settings.github.page.actions.switchTo': '切换到',
|
'settings.github.page.actions.switchTo': '切换到',
|
||||||
|
|||||||
@@ -1239,6 +1239,8 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'header.github.avatarWithLogin': '{login} 头像',
|
'header.github.avatarWithLogin': '{login} 头像',
|
||||||
'header.github.avatar': 'GitHub 头像',
|
'header.github.avatar': 'GitHub 头像',
|
||||||
'header.github.accountsTitle': 'GitHub 账户',
|
'header.github.accountsTitle': 'GitHub 账户',
|
||||||
|
'header.github.accountSource.oauth': 'OAuth',
|
||||||
|
'header.github.accountSource.cli': 'CLI',
|
||||||
'header.services.openWithCurrent': '打开实例、用量和 MCP(当前:{current})',
|
'header.services.openWithCurrent': '打开实例、用量和 MCP(当前:{current})',
|
||||||
'header.services.open': '打开服务、用量和 MCP',
|
'header.services.open': '打开服务、用量和 MCP',
|
||||||
'header.services.tooltip.currentInstanceWithShortcuts': '当前实例:{current}({toggle};下一标签 {nextTab})',
|
'header.services.tooltip.currentInstanceWithShortcuts': '当前实例:{current}({toggle};下一标签 {nextTab})',
|
||||||
|
|||||||
@@ -1278,6 +1278,8 @@
|
|||||||
'settings.github.page.label.unknownUser': '未知',
|
'settings.github.page.label.unknownUser': '未知',
|
||||||
'settings.github.page.label.scopes': '作用域:{value}',
|
'settings.github.page.label.scopes': '作用域:{value}',
|
||||||
'settings.github.page.label.otherAccounts': '其他帳號',
|
'settings.github.page.label.otherAccounts': '其他帳號',
|
||||||
|
'settings.github.page.accountSource.oauth': 'OAuth',
|
||||||
|
'settings.github.page.accountSource.cli': 'CLI',
|
||||||
'settings.github.page.actions.disconnect': '中斷連線',
|
'settings.github.page.actions.disconnect': '中斷連線',
|
||||||
'settings.github.page.actions.connect': '連線 GitHub',
|
'settings.github.page.actions.connect': '連線 GitHub',
|
||||||
'settings.github.page.actions.switchTo': '切換到',
|
'settings.github.page.actions.switchTo': '切換到',
|
||||||
|
|||||||
@@ -1249,6 +1249,8 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'header.github.avatarWithLogin': '{login} 頭像',
|
'header.github.avatarWithLogin': '{login} 頭像',
|
||||||
'header.github.avatar': 'GitHub 頭像',
|
'header.github.avatar': 'GitHub 頭像',
|
||||||
'header.github.accountsTitle': 'GitHub 帳戶',
|
'header.github.accountsTitle': 'GitHub 帳戶',
|
||||||
|
'header.github.accountSource.oauth': 'OAuth',
|
||||||
|
'header.github.accountSource.cli': 'CLI',
|
||||||
'header.services.openWithCurrent': '開啟實例、用量和 MCP(目前:{current})',
|
'header.services.openWithCurrent': '開啟實例、用量和 MCP(目前:{current})',
|
||||||
'header.services.open': '開啟服務、用量和 MCP',
|
'header.services.open': '開啟服務、用量和 MCP',
|
||||||
'header.services.tooltip.currentInstanceWithShortcuts': '目前實例:{current}({toggle};下一分頁 {nextTab})',
|
'header.services.tooltip.currentInstanceWithShortcuts': '目前實例:{current}({toggle};下一分頁 {nextTab})',
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const SETTINGS_FILE = path.join(OPENCHAMBER_DATA_DIR, 'settings.json');
|
|||||||
|
|
||||||
const DEFAULT_GITHUB_CLIENT_ID = 'Ov23lizomPOC3eFYo56r';
|
const DEFAULT_GITHUB_CLIENT_ID = 'Ov23lizomPOC3eFYo56r';
|
||||||
const DEFAULT_GITHUB_SCOPES = 'repo read:org workflow read:user user:email';
|
const DEFAULT_GITHUB_SCOPES = 'repo read:org workflow read:user user:email';
|
||||||
|
export const GH_CLI_ACCOUNT_ID = 'gh-cli';
|
||||||
|
|
||||||
function ensureStorageDir() {
|
function ensureStorageDir() {
|
||||||
if (!fs.existsSync(STORAGE_DIR)) {
|
if (!fs.existsSync(STORAGE_DIR)) {
|
||||||
@@ -159,6 +160,34 @@ function writeAuthList(list) {
|
|||||||
writeJsonFile(list);
|
writeJsonFile(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readSettingsFile() {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(SETTINGS_FILE)) {
|
||||||
|
return JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8')) || {};
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeSettingsFile(settings) {
|
||||||
|
ensureStorageDir();
|
||||||
|
const tmpFile = `${SETTINGS_FILE}.${process.pid}.${Date.now()}.tmp`;
|
||||||
|
fs.writeFileSync(tmpFile, JSON.stringify(settings, null, 2), 'utf8');
|
||||||
|
try {
|
||||||
|
fs.chmodSync(tmpFile, 0o600);
|
||||||
|
} catch {
|
||||||
|
// best-effort
|
||||||
|
}
|
||||||
|
fs.renameSync(tmpFile, SETTINGS_FILE);
|
||||||
|
try {
|
||||||
|
fs.chmodSync(SETTINGS_FILE, 0o600);
|
||||||
|
} catch {
|
||||||
|
// best-effort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getGitHubAuth() {
|
export function getGitHubAuth() {
|
||||||
const list = readAuthList();
|
const list = readAuthList();
|
||||||
if (!list.length) {
|
if (!list.length) {
|
||||||
@@ -237,6 +266,7 @@ export function activateGitHubAuth(accountId) {
|
|||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
setGhCliActive(false);
|
||||||
list.forEach((entry, idx) => {
|
list.forEach((entry, idx) => {
|
||||||
entry.current = idx === index;
|
entry.current = idx === index;
|
||||||
});
|
});
|
||||||
@@ -307,39 +337,25 @@ export function getGitHubScopes() {
|
|||||||
export const GITHUB_AUTH_FILE = STORAGE_FILE;
|
export const GITHUB_AUTH_FILE = STORAGE_FILE;
|
||||||
|
|
||||||
export function isGhCliDisabled() {
|
export function isGhCliDisabled() {
|
||||||
try {
|
return Boolean(readSettingsFile()?.ghCliDisabled);
|
||||||
if (fs.existsSync(SETTINGS_FILE)) {
|
|
||||||
const parsed = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8'));
|
|
||||||
return Boolean(parsed?.ghCliDisabled);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setGhCliDisabled(disabled) {
|
export function setGhCliDisabled(disabled) {
|
||||||
ensureStorageDir();
|
const settings = readSettingsFile();
|
||||||
let settings = {};
|
|
||||||
try {
|
|
||||||
if (fs.existsSync(SETTINGS_FILE)) {
|
|
||||||
settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8')) || {};
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
settings.ghCliDisabled = Boolean(disabled);
|
settings.ghCliDisabled = Boolean(disabled);
|
||||||
const tmpFile = `${SETTINGS_FILE}.${process.pid}.${Date.now()}.tmp`;
|
if (settings.ghCliDisabled) {
|
||||||
fs.writeFileSync(tmpFile, JSON.stringify(settings, null, 2), 'utf8');
|
settings.ghCliActive = false;
|
||||||
try {
|
|
||||||
fs.chmodSync(tmpFile, 0o600);
|
|
||||||
} catch {
|
|
||||||
// best-effort
|
|
||||||
}
|
|
||||||
fs.renameSync(tmpFile, SETTINGS_FILE);
|
|
||||||
try {
|
|
||||||
fs.chmodSync(SETTINGS_FILE, 0o600);
|
|
||||||
} catch {
|
|
||||||
// best-effort
|
|
||||||
}
|
}
|
||||||
|
writeSettingsFile(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isGhCliActive() {
|
||||||
|
const settings = readSettingsFile();
|
||||||
|
return !settings?.ghCliDisabled && Boolean(settings?.ghCliActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setGhCliActive(active) {
|
||||||
|
const settings = readSettingsFile();
|
||||||
|
settings.ghCliActive = Boolean(active) && !settings.ghCliDisabled;
|
||||||
|
writeSettingsFile(settings);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ export {
|
|||||||
clearGitHubAuth,
|
clearGitHubAuth,
|
||||||
getGitHubClientId,
|
getGitHubClientId,
|
||||||
getGitHubScopes,
|
getGitHubScopes,
|
||||||
|
GH_CLI_ACCOUNT_ID,
|
||||||
isGhCliDisabled,
|
isGhCliDisabled,
|
||||||
|
isGhCliActive,
|
||||||
|
setGhCliActive,
|
||||||
setGhCliDisabled,
|
setGhCliDisabled,
|
||||||
GITHUB_AUTH_FILE,
|
GITHUB_AUTH_FILE,
|
||||||
} from './auth.js';
|
} from './auth.js';
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Octokit } from '@octokit/rest';
|
import { Octokit } from '@octokit/rest';
|
||||||
import { getGitHubAuth, isGhCliDisabled } from './auth.js';
|
import { getGitHubAuth, isGhCliActive, isGhCliDisabled } from './auth.js';
|
||||||
import { getGhCliToken } from './gh-cli-credential.js';
|
import { getGhCliToken } from './gh-cli-credential.js';
|
||||||
|
|
||||||
export function getOctokitOrNull() {
|
export function getOctokitOrNull() {
|
||||||
const auth = getGitHubAuth();
|
const auth = getGitHubAuth();
|
||||||
const token = auth?.accessToken || (!isGhCliDisabled() ? getGhCliToken() : null);
|
const ghToken = !isGhCliDisabled() ? getGhCliToken() : null;
|
||||||
|
const token = isGhCliActive() ? ghToken || auth?.accessToken : auth?.accessToken || ghToken;
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,17 +76,18 @@ export function registerGitHubRoutes(app) {
|
|||||||
|
|
||||||
app.get('/api/github/auth/status', async (_req, res) => {
|
app.get('/api/github/auth/status', async (_req, res) => {
|
||||||
try {
|
try {
|
||||||
const { getGitHubAuth, getOctokitOrNull, clearGitHubAuth, getGitHubAuthAccounts, isGhCliDisabled } = await getGitHubLibraries();
|
const { getGitHubAuth, getOctokitOrNull, clearGitHubAuth, getGitHubAuthAccounts, GH_CLI_ACCOUNT_ID, isGhCliActive, isGhCliDisabled, setGhCliActive } = await getGitHubLibraries();
|
||||||
const { getGhCliToken } = await import('./gh-cli-credential.js');
|
const { getGhCliToken } = await import('./gh-cli-credential.js');
|
||||||
|
|
||||||
const auth = getGitHubAuth();
|
const auth = getGitHubAuth();
|
||||||
const accounts = getGitHubAuthAccounts();
|
let accounts = getGitHubAuthAccounts();
|
||||||
const ghCliDisabled = isGhCliDisabled();
|
const ghCliDisabled = isGhCliDisabled();
|
||||||
|
const ghCliActive = isGhCliActive();
|
||||||
const ghToken = getGhCliToken();
|
const ghToken = getGhCliToken();
|
||||||
const usingOwnToken = Boolean(auth?.accessToken);
|
const usingOwnToken = Boolean(auth?.accessToken);
|
||||||
let ghCliUser = null;
|
let ghCliUser = null;
|
||||||
|
|
||||||
if (ghToken !== null && !ghCliDisabled && usingOwnToken) {
|
if (ghToken !== null && !ghCliDisabled) {
|
||||||
try {
|
try {
|
||||||
const { Octokit } = await import('@octokit/rest');
|
const { Octokit } = await import('@octokit/rest');
|
||||||
ghCliUser = await getGitHubUserSummary(new Octokit({ auth: ghToken }));
|
ghCliUser = await getGitHubUserSummary(new Octokit({ auth: ghToken }));
|
||||||
@@ -94,11 +95,26 @@ export function registerGitHubRoutes(app) {
|
|||||||
ghCliUser = null;
|
ghCliUser = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ghCliActive && !ghCliUser) {
|
||||||
|
setGhCliActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ghCliCurrent = ghToken !== null && !ghCliDisabled && Boolean(ghCliUser) && (ghCliActive || !usingOwnToken);
|
||||||
|
if (ghCliUser) {
|
||||||
|
accounts = accounts
|
||||||
|
.map((account) => ({ ...account, current: ghCliCurrent ? false : Boolean(account.current) }))
|
||||||
|
.concat({
|
||||||
|
id: GH_CLI_ACCOUNT_ID,
|
||||||
|
user: ghCliUser,
|
||||||
|
current: ghCliCurrent,
|
||||||
|
source: 'gh-cli',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const buildGhCli = (activeUser = null) => ({
|
const buildGhCli = (activeUser = null) => ({
|
||||||
available: ghToken !== null,
|
available: ghToken !== null,
|
||||||
disabled: ghCliDisabled,
|
disabled: ghCliDisabled,
|
||||||
active: !usingOwnToken && ghToken !== null && !ghCliDisabled,
|
active: ghCliCurrent,
|
||||||
...(!ghCliDisabled && (activeUser || ghCliUser) ? { user: activeUser || ghCliUser } : {}),
|
...(!ghCliDisabled && (activeUser || ghCliUser) ? { user: activeUser || ghCliUser } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -119,14 +135,12 @@ export function registerGitHubRoutes(app) {
|
|||||||
|
|
||||||
const fallback = usingOwnToken ? auth.user : null;
|
const fallback = usingOwnToken ? auth.user : null;
|
||||||
const mergedUser = user || fallback;
|
const mergedUser = user || fallback;
|
||||||
const ghIsActive = !usingOwnToken && ghToken !== null && !ghCliDisabled;
|
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
connected: true,
|
connected: true,
|
||||||
user: mergedUser,
|
user: mergedUser,
|
||||||
scope: usingOwnToken ? auth.scope : undefined,
|
scope: ghCliCurrent ? undefined : auth?.scope,
|
||||||
accounts,
|
accounts,
|
||||||
ghCli: buildGhCli(ghIsActive ? mergedUser : null),
|
ghCli: buildGhCli(ghCliCurrent ? mergedUser : null),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to get GitHub auth status:', error);
|
console.error('Failed to get GitHub auth status:', error);
|
||||||
@@ -238,25 +252,70 @@ export function registerGitHubRoutes(app) {
|
|||||||
|
|
||||||
app.post('/api/github/auth/activate', async (req, res) => {
|
app.post('/api/github/auth/activate', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { activateGitHubAuth, getGitHubAuth, getOctokitOrNull, clearGitHubAuth, getGitHubAuthAccounts } = await getGitHubLibraries();
|
const { activateGitHubAuth, getGitHubAuth, getOctokitOrNull, clearGitHubAuth, getGitHubAuthAccounts, GH_CLI_ACCOUNT_ID, isGhCliDisabled, setGhCliActive } = await getGitHubLibraries();
|
||||||
const accountId = typeof req.body?.accountId === 'string' ? req.body.accountId : '';
|
const accountId = typeof req.body?.accountId === 'string' ? req.body.accountId : '';
|
||||||
if (!accountId) {
|
if (!accountId) {
|
||||||
return res.status(400).json({ error: 'accountId is required' });
|
return res.status(400).json({ error: 'accountId is required' });
|
||||||
}
|
}
|
||||||
|
if (accountId === GH_CLI_ACCOUNT_ID) {
|
||||||
|
const { getGhCliToken } = await import('./gh-cli-credential.js');
|
||||||
|
const ghToken = !isGhCliDisabled() ? getGhCliToken() : null;
|
||||||
|
if (!ghToken) {
|
||||||
|
return res.status(404).json({ error: 'GitHub CLI account not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { Octokit } = await import('@octokit/rest');
|
||||||
|
const user = await getGitHubUserSummary(new Octokit({ auth: ghToken }));
|
||||||
|
setGhCliActive(true);
|
||||||
|
const accounts = getGitHubAuthAccounts()
|
||||||
|
.map((account) => ({ ...account, current: false }))
|
||||||
|
.concat({ id: GH_CLI_ACCOUNT_ID, user, current: true, source: 'gh-cli' });
|
||||||
|
return res.json({
|
||||||
|
connected: true,
|
||||||
|
user,
|
||||||
|
accounts,
|
||||||
|
ghCli: {
|
||||||
|
available: true,
|
||||||
|
disabled: false,
|
||||||
|
active: true,
|
||||||
|
user,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const activated = activateGitHubAuth(accountId);
|
const activated = activateGitHubAuth(accountId);
|
||||||
if (!activated) {
|
if (!activated) {
|
||||||
return res.status(404).json({ error: 'GitHub account not found' });
|
return res.status(404).json({ error: 'GitHub account not found' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const auth = getGitHubAuth();
|
const auth = getGitHubAuth();
|
||||||
const accounts = getGitHubAuthAccounts();
|
let accounts = getGitHubAuthAccounts();
|
||||||
if (!auth?.accessToken) {
|
if (!auth?.accessToken) {
|
||||||
return res.json({ connected: false, accounts });
|
return res.json({ connected: false, accounts });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { getGhCliToken } = await import('./gh-cli-credential.js');
|
||||||
|
const ghCliDisabled = isGhCliDisabled();
|
||||||
|
const ghToken = !ghCliDisabled ? getGhCliToken() : null;
|
||||||
|
let ghCliUser = null;
|
||||||
|
if (ghToken) {
|
||||||
|
try {
|
||||||
|
const { Octokit } = await import('@octokit/rest');
|
||||||
|
ghCliUser = await getGitHubUserSummary(new Octokit({ auth: ghToken }));
|
||||||
|
accounts = accounts.concat({
|
||||||
|
id: GH_CLI_ACCOUNT_ID,
|
||||||
|
user: ghCliUser,
|
||||||
|
current: false,
|
||||||
|
source: 'gh-cli',
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
ghCliUser = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const octokit = getOctokitOrNull();
|
const octokit = getOctokitOrNull();
|
||||||
if (!octokit) {
|
if (!octokit) {
|
||||||
return res.json({ connected: false, accounts });
|
return res.json({ connected: false, accounts, ghCli: { available: ghToken !== null, disabled: ghCliDisabled, active: false, ...(ghCliUser ? { user: ghCliUser } : {}) } });
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = auth.user || null;
|
let user = auth.user || null;
|
||||||
@@ -274,6 +333,12 @@ export function registerGitHubRoutes(app) {
|
|||||||
user,
|
user,
|
||||||
scope: auth.scope,
|
scope: auth.scope,
|
||||||
accounts,
|
accounts,
|
||||||
|
ghCli: {
|
||||||
|
available: ghToken !== null,
|
||||||
|
disabled: ghCliDisabled,
|
||||||
|
active: false,
|
||||||
|
...(ghCliUser ? { user: ghCliUser } : {}),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to activate GitHub account:', error);
|
console.error('Failed to activate GitHub account:', error);
|
||||||
|
|||||||
Reference in New Issue
Block a user