v2.2.15: Astra Office Refactor & Multi-Service Integration
This commit is contained in:
@@ -664,6 +664,22 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// 같은 pixelOfficeUpdate 메시지 스트림을 공유하므로 백엔드 변경 최소.
|
||||
provider?.openPixelOfficePanel();
|
||||
}),
|
||||
// Google Calendar (iCal 읽기 전용) — 셋업 / 재연결 / 해제 / 즉시 새로고침.
|
||||
vscode.commands.registerCommand('g1nation.calendar.connect', async () => {
|
||||
await runConnectGoogleCalendarIcal(context);
|
||||
}),
|
||||
vscode.commands.registerCommand('g1nation.calendar.refresh', async () => {
|
||||
const { refreshCalendarCache } = await import('./features/calendar');
|
||||
const r = await refreshCalendarCache(context);
|
||||
if (r.ok) {
|
||||
vscode.window.showInformationMessage(`📅 캘린더 ${r.count}개 일정을 회사 컨텍스트에 동기화했습니다.`);
|
||||
} else {
|
||||
vscode.window.showErrorMessage(r.error || 'Calendar 새로고침 실패');
|
||||
}
|
||||
}),
|
||||
vscode.commands.registerCommand('g1nation.calendar.connectOAuth', async () => {
|
||||
await runConnectGoogleCalendarOAuth(context);
|
||||
}),
|
||||
);
|
||||
|
||||
/** All lesson/playbook/qa-finding cards in the active brain. Uses the brain index for the lesson-kind
|
||||
@@ -891,6 +907,197 @@ export async function deactivate() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Google Calendar (iCal 읽기 전용) 연결 마법사.
|
||||
*
|
||||
* 사용자 흐름:
|
||||
* 1. 이미 셋업 됐으면 "연결 해제 / URL 변경 / 지금 새로고침 / 취소" 선택지 노출
|
||||
* 2. 새로 셋업: Google Calendar 설정 페이지 외부 브라우저로 열고 → 비공개 iCal URL 입력
|
||||
* 3. 입력값을 globalState 에 저장 후 즉시 한 번 새로고침 실행 → 캐시 파일 생성 안내
|
||||
*
|
||||
* OAuth 가 아닌 read-only iCal 만 — 셋업 3분, 토큰 관리 없음.
|
||||
*/
|
||||
async function runConnectGoogleCalendarIcal(context: vscode.ExtensionContext) {
|
||||
const { readCalendarConfig, writeCalendarConfig, refreshCalendarCache } =
|
||||
await import('./features/calendar');
|
||||
const cur = readCalendarConfig(context);
|
||||
if (cur.icalUrl) {
|
||||
const choice = await vscode.window.showInformationMessage(
|
||||
`📅 이미 연결됨${cur.lastFetchAt ? ` (마지막 동기화: ${cur.lastFetchAt.slice(0, 16)})` : ''}`,
|
||||
{ modal: false },
|
||||
'지금 새로고침',
|
||||
'URL 변경',
|
||||
'연결 해제',
|
||||
'취소',
|
||||
);
|
||||
if (!choice || choice === '취소') return;
|
||||
if (choice === '지금 새로고침') {
|
||||
const r = await refreshCalendarCache(context);
|
||||
if (r.ok) vscode.window.showInformationMessage(`📅 ${r.count}개 일정 동기화 완료.`);
|
||||
else vscode.window.showErrorMessage(r.error || '새로고침 실패');
|
||||
return;
|
||||
}
|
||||
if (choice === '연결 해제') {
|
||||
await writeCalendarConfig(context, { icalUrl: '', lastFetchAt: undefined });
|
||||
vscode.window.showInformationMessage('Google Calendar 연결 해제됨. 캐시 파일은 그대로 둡니다.');
|
||||
return;
|
||||
}
|
||||
// URL 변경 → 아래 입력 흐름으로 fall through
|
||||
} else {
|
||||
const intro = await vscode.window.showInformationMessage(
|
||||
'📅 Google Calendar 연결 (읽기 전용, 셋업 3분)\n\n비공개 iCal URL 1개만 있으면 됩니다. OAuth 없음.\n\n계속할까요?',
|
||||
{ modal: true },
|
||||
'시작',
|
||||
'Google Calendar 설정 페이지 열기',
|
||||
'취소',
|
||||
);
|
||||
if (!intro || intro === '취소') return;
|
||||
if (intro === 'Google Calendar 설정 페이지 열기') {
|
||||
await vscode.env.openExternal(
|
||||
vscode.Uri.parse('https://calendar.google.com/calendar/u/0/r/settings'),
|
||||
);
|
||||
const back = await vscode.window.showInformationMessage(
|
||||
'1. 왼쪽에서 본인 캘린더 클릭 → "캘린더 통합" 섹션\n2. "비공개 주소(iCal 형식)" 옆 복사 버튼 클릭\n3. URL 복사한 뒤 ↓',
|
||||
{ modal: true },
|
||||
'복사함 — URL 붙여넣기',
|
||||
'취소',
|
||||
);
|
||||
if (back !== '복사함 — URL 붙여넣기') return;
|
||||
}
|
||||
}
|
||||
|
||||
const url = await vscode.window.showInputBox({
|
||||
title: 'Google Calendar 비공개 iCal URL',
|
||||
prompt: 'calendar.google.com/calendar/ical/.../private-XXX/basic.ics 형태',
|
||||
placeHolder: 'https://calendar.google.com/calendar/ical/...',
|
||||
value: cur.icalUrl,
|
||||
password: true,
|
||||
ignoreFocusOut: true,
|
||||
validateInput: (v) => {
|
||||
const t = (v || '').trim();
|
||||
if (!t) return '비어있어요';
|
||||
if (!/^https?:\/\//.test(t)) return 'http:// 또는 https:// 로 시작해야 합니다.';
|
||||
return null;
|
||||
},
|
||||
});
|
||||
if (!url) return;
|
||||
|
||||
await writeCalendarConfig(context, { icalUrl: url.trim() });
|
||||
const r = await refreshCalendarCache(context);
|
||||
if (r.ok) {
|
||||
vscode.window.showInformationMessage(
|
||||
`✅ 연결 완료 — ${r.count}개 일정을 회사 컨텍스트에 동기화했습니다.\n\n이제 기업 모드에서 모든 에이전트가 다가오는 일정을 자동으로 참고합니다.`,
|
||||
);
|
||||
} else {
|
||||
vscode.window.showErrorMessage(
|
||||
`URL 저장은 됐지만 첫 새로고침 실패: ${r.error}\n\nURL 이 정확한지 다시 확인해주세요.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Google Calendar OAuth (쓰기) 연결 마법사.
|
||||
*
|
||||
* iCal 마법사와 별도 — 이쪽은 agent 가 회의록 보고 자동으로 일정 *만들* 수 있게 한다.
|
||||
* 셋업 5~10분: Google Cloud Console 에서 OAuth Client ID/Secret 발급 → 본 마법사가
|
||||
* loopback OAuth 흐름 실행 → refresh token 받아 globalState 저장.
|
||||
*/
|
||||
async function runConnectGoogleCalendarOAuth(context: vscode.ExtensionContext) {
|
||||
const { readCalendarConfig, writeCalendarConfig, runOAuthLoopback, fetchUserEmail } =
|
||||
await import('./features/calendar');
|
||||
const cur = readCalendarConfig(context);
|
||||
const already = !!(cur.clientId && cur.clientSecret && cur.refreshToken);
|
||||
if (already) {
|
||||
const choice = await vscode.window.showInformationMessage(
|
||||
`✅ 이미 OAuth 연결됨${cur.connectedAs ? ` (${cur.connectedAs})` : ''}`,
|
||||
{ modal: false },
|
||||
'재연결',
|
||||
'연결 해제',
|
||||
'취소',
|
||||
);
|
||||
if (!choice || choice === '취소') return;
|
||||
if (choice === '연결 해제') {
|
||||
await writeCalendarConfig(context, {
|
||||
clientId: undefined, clientSecret: undefined, refreshToken: undefined,
|
||||
accessToken: undefined, accessTokenExpiresAt: undefined,
|
||||
connectedAs: undefined, connectedAt: undefined,
|
||||
});
|
||||
vscode.window.showInformationMessage(
|
||||
'OAuth 연결 해제. https://myaccount.google.com/permissions 에서도 권한을 직접 회수할 수 있습니다.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
// 재연결 → 아래 flow
|
||||
} else {
|
||||
const intro = await vscode.window.showInformationMessage(
|
||||
'📅 Google Calendar 쓰기 연결 (OAuth, 5~10분)\n\n회의록을 받으면 agent 가 자동으로 일정을 생성하게 됩니다.\n\n1단계: Google Cloud Console 에서 OAuth Client ID 발급 (수동 클릭, 가이드 따라)\n2단계: ID + Secret 붙여넣기\n3단계: 브라우저 로그인',
|
||||
{ modal: true },
|
||||
'시작',
|
||||
'Cloud Console 먼저 열기',
|
||||
'취소',
|
||||
);
|
||||
if (!intro || intro === '취소') return;
|
||||
if (intro === 'Cloud Console 먼저 열기') {
|
||||
await vscode.env.openExternal(vscode.Uri.parse('https://console.cloud.google.com/apis/credentials'));
|
||||
const back = await vscode.window.showInformationMessage(
|
||||
'아래 절차 마치고 돌아오세요:\n\n1. 새 프로젝트 만들기 (또는 기존)\n2. APIs & Services → Library → "Google Calendar API" 활성화\n3. OAuth 동의 화면 — External, Test users 에 본인 이메일\n4. Credentials → Create OAuth 2.0 Client ID → "Desktop app"\n5. Client ID + Client Secret 복사',
|
||||
{ modal: true },
|
||||
'다 됐음 →',
|
||||
'취소',
|
||||
);
|
||||
if (back !== '다 됐음 →') return;
|
||||
}
|
||||
}
|
||||
|
||||
const clientId = await vscode.window.showInputBox({
|
||||
title: 'Google OAuth Client ID',
|
||||
prompt: 'Credentials 페이지에서 복사한 Client ID',
|
||||
placeHolder: 'xxxxxxxx.apps.googleusercontent.com',
|
||||
value: cur.clientId,
|
||||
ignoreFocusOut: true,
|
||||
validateInput: (v) => (v || '').trim() ? null : '비어있어요',
|
||||
});
|
||||
if (!clientId) return;
|
||||
const clientSecret = await vscode.window.showInputBox({
|
||||
title: 'Google OAuth Client Secret',
|
||||
prompt: '같은 화면의 Client Secret',
|
||||
placeHolder: 'GOCSPX-...',
|
||||
value: cur.clientSecret,
|
||||
password: true,
|
||||
ignoreFocusOut: true,
|
||||
validateInput: (v) => (v || '').trim() ? null : '비어있어요',
|
||||
});
|
||||
if (!clientSecret) return;
|
||||
|
||||
await vscode.window.withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: '🔐 Google 로그인 대기 중…',
|
||||
cancellable: true,
|
||||
}, async (progress, cancelToken) => {
|
||||
progress.report({ message: '브라우저에서 Google 로그인 진행하세요 (최대 5분 대기)' });
|
||||
const result = await runOAuthLoopback(clientId.trim(), clientSecret.trim(), cancelToken);
|
||||
if (!result.ok) {
|
||||
vscode.window.showErrorMessage(`OAuth 실패: ${result.error}`);
|
||||
return;
|
||||
}
|
||||
const email = await fetchUserEmail(result.accessToken);
|
||||
await writeCalendarConfig(context, {
|
||||
clientId: clientId.trim(),
|
||||
clientSecret: clientSecret.trim(),
|
||||
refreshToken: result.refreshToken,
|
||||
accessToken: result.accessToken,
|
||||
accessTokenExpiresAt: result.expiresAt,
|
||||
calendarId: cur.calendarId ?? 'primary',
|
||||
defaultDurationMinutes: cur.defaultDurationMinutes ?? 60,
|
||||
connectedAs: email,
|
||||
connectedAt: new Date().toISOString(),
|
||||
});
|
||||
vscode.window.showInformationMessage(
|
||||
`✅ Google Calendar 쓰기 연결 완료!${email ? ' (' + email + ')' : ''}\n\n이제 회의록을 보내거나 due 가 있는 작업을 알려주면 agent 가 자동으로 일정을 생성합니다.`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async function runInitialSetup(context: vscode.ExtensionContext) {
|
||||
// 이미 사용자가 URL을 설정했다면 자동 감지를 스킵
|
||||
const existingUrl = vscode.workspace.getConfiguration('g1nation').get<string>('ollamaUrl');
|
||||
|
||||
Reference in New Issue
Block a user