NotebookLM CLI 로그인 버튼 — nlm 실행 파일 자동 탐색
- 사이드바 도구 메뉴에 'NotebookLM 로그인' 버튼 추가, 파이썬 Scripts 폴더에서 nlm(.exe) 자동 탐색(버전 하드코딩 없음) 후 login 인자로 실행 - g1nation.notebookLmCliPath 설정으로 수동 경로 지정도 가능
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"result": "직답 결과 — single-pass mock 응답입니다.",
|
"result": "직답 결과 — single-pass mock 응답입니다.",
|
||||||
"createdAt": 1785981204343,
|
"createdAt": 1786009859146,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"result": "---\nid: wiki_on\ndate: 2026-08-06T01:53:24.345Z\ntype: knowledge_artifact\nstandard: P-Reinforce v3.0\ntags: [automated, connect_ai, brain_sync]\n---\n\n## 📌 Brief Summary\n직답 결과 — single-pass mock 응답입니다.\n\n직답 결과 — single-pass mock 응답입니다.\n---\n## 🛡️ Reliability & Audit Summary\n> [!NOTE]\n> 이 문서는 ConnectAI의 **Intelligent Resilience** 엔진에 의해 검증 및 정제되었습니다.\n\n| Metric | Value | Status |\n| :--- | :--- | :--- |\n| **Conflict Risk** | `0/100` | ✅ Low |\n| **Fallbacks Used** | `0` | ✅ None |\n| **Auto Retries** | `0` | ✅ Stable |\n| **Deduplication** | `0` | Standard |\n| **Processing Time** | `0.0s` | ✅ Fast |\n\n### 🔍 Decision Audit Trail\n- **[DIRECT]** 답변 작성 중... (단일 호출 fast-path) (34ms)\n",
|
"result": "---\nid: wiki_on\ndate: 2026-08-06T09:50:59.150Z\ntype: knowledge_artifact\nstandard: P-Reinforce v3.0\ntags: [automated, connect_ai, brain_sync]\n---\n\n## 📌 Brief Summary\n직답 결과 — single-pass mock 응답입니다.\n\n직답 결과 — single-pass mock 응답입니다.\n---\n## 🛡️ Reliability & Audit Summary\n> [!NOTE]\n> 이 문서는 ConnectAI의 **Intelligent Resilience** 엔진에 의해 검증 및 정제되었습니다.\n\n| Metric | Value | Status |\n| :--- | :--- | :--- |\n| **Conflict Risk** | `0/100` | ✅ Low |\n| **Fallbacks Used** | `0` | ✅ None |\n| **Auto Retries** | `0` | ✅ Stable |\n| **Deduplication** | `0` | Standard |\n| **Processing Time** | `0.0s` | ✅ Fast |\n\n### 🔍 Decision Audit Trail\n- **[DIRECT]** 답변 작성 중... (단일 호출 fast-path) (23ms)\n",
|
||||||
"createdAt": 1785981204345,
|
"createdAt": 1786009859151,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"missionId": "wiki_on",
|
"missionId": "wiki_on",
|
||||||
"status": "completed",
|
"status": "completed",
|
||||||
"startTime": "2026-08-06T01:53:24.307Z",
|
"startTime": "2026-08-06T09:50:59.117Z",
|
||||||
"totalElapsedMs": 40,
|
"totalElapsedMs": 40,
|
||||||
"results": {
|
"results": {
|
||||||
"direct": "직답 결과 — single-pass mock 응답입니다."
|
"direct": "직답 결과 — single-pass mock 응답입니다."
|
||||||
@@ -12,16 +12,16 @@
|
|||||||
{
|
{
|
||||||
"from": "idle",
|
"from": "idle",
|
||||||
"to": "direct",
|
"to": "direct",
|
||||||
"durationMs": 34,
|
"durationMs": 23,
|
||||||
"message": "답변 작성 중... (단일 호출 fast-path)",
|
"message": "답변 작성 중... (단일 호출 fast-path)",
|
||||||
"ts": "2026-08-06T01:53:24.341Z"
|
"ts": "2026-08-06T09:50:59.140Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "direct",
|
"from": "direct",
|
||||||
"to": "completed",
|
"to": "completed",
|
||||||
"durationMs": 6,
|
"durationMs": 14,
|
||||||
"message": "미션 완료",
|
"message": "미션 완료",
|
||||||
"ts": "2026-08-06T01:53:24.347Z"
|
"ts": "2026-08-06T09:50:59.154Z"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"resilienceMetrics": {
|
"resilienceMetrics": {
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"result": "Final report with inconsistencies. This should be long enough to pass validation.",
|
"result": "Final report with inconsistencies. This should be long enough to pass validation.",
|
||||||
"createdAt": 1785981213594,
|
"createdAt": 1786009865515,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"result": "Final report with inconsistencies. This should be long enough to pass validation.",
|
"result": "Final report with inconsistencies. This should be long enough to pass validation.",
|
||||||
"createdAt": 1785981213593,
|
"createdAt": 1786009865514,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"result": "[{\"heading\":\"본문\",\"scope\":\"전체 답변\"}]",
|
"result": "[{\"heading\":\"본문\",\"scope\":\"전체 답변\"}]",
|
||||||
"createdAt": 1785981213590,
|
"createdAt": 1786009865510,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
"result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
||||||
"createdAt": 1785981213592,
|
"createdAt": 1786009865512,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+8
-8
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"missionId": "stress_conflict_1785981213570",
|
"missionId": "stress_conflict_1786009865491",
|
||||||
"status": "completed",
|
"status": "completed",
|
||||||
"startTime": "2026-08-06T01:53:33.570Z",
|
"startTime": "2026-08-06T09:51:05.491Z",
|
||||||
"totalElapsedMs": 25,
|
"totalElapsedMs": 25,
|
||||||
"results": {
|
"results": {
|
||||||
"outline": "[{\"heading\":\"본문\",\"scope\":\"전체 답변\"}]",
|
"outline": "[{\"heading\":\"본문\",\"scope\":\"전체 답변\"}]",
|
||||||
@@ -14,30 +14,30 @@
|
|||||||
{
|
{
|
||||||
"from": "idle",
|
"from": "idle",
|
||||||
"to": "outline",
|
"to": "outline",
|
||||||
"durationMs": 19,
|
"durationMs": 18,
|
||||||
"message": "답변 구조 잡는 중...",
|
"message": "답변 구조 잡는 중...",
|
||||||
"ts": "2026-08-06T01:53:33.589Z"
|
"ts": "2026-08-06T09:51:05.509Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "outline",
|
"from": "outline",
|
||||||
"to": "section",
|
"to": "section",
|
||||||
"durationMs": 2,
|
"durationMs": 2,
|
||||||
"message": "본문 작성 중...",
|
"message": "본문 작성 중...",
|
||||||
"ts": "2026-08-06T01:53:33.591Z"
|
"ts": "2026-08-06T09:51:05.511Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "section",
|
"from": "section",
|
||||||
"to": "polish",
|
"to": "polish",
|
||||||
"durationMs": 1,
|
"durationMs": 2,
|
||||||
"message": "최종 다듬기 중...",
|
"message": "최종 다듬기 중...",
|
||||||
"ts": "2026-08-06T01:53:33.592Z"
|
"ts": "2026-08-06T09:51:05.513Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "polish",
|
"from": "polish",
|
||||||
"to": "completed",
|
"to": "completed",
|
||||||
"durationMs": 3,
|
"durationMs": 3,
|
||||||
"message": "미션 완료",
|
"message": "미션 완료",
|
||||||
"ts": "2026-08-06T01:53:33.595Z"
|
"ts": "2026-08-06T09:51:05.516Z"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"resilienceMetrics": {
|
"resilienceMetrics": {
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
<button class="hdr-menu-item" id="brainBtn" data-tooltip="두뇌(지식) 폴더 변경사항을 git commit + push">두뇌 동기화</button>
|
<button class="hdr-menu-item" id="brainBtn" data-tooltip="두뇌(지식) 폴더 변경사항을 git commit + push">두뇌 동기화</button>
|
||||||
<button class="hdr-menu-item" id="nlmBridgeStartBtn" data-tooltip="Datacollector Research(NotebookLM)용 로컬 백엔드를 실행합니다 — 터미널 조작 불필요">NotebookLM 백엔드 실행</button>
|
<button class="hdr-menu-item" id="nlmBridgeStartBtn" data-tooltip="Datacollector Research(NotebookLM)용 로컬 백엔드를 실행합니다 — 터미널 조작 불필요">NotebookLM 백엔드 실행</button>
|
||||||
<button class="hdr-menu-item" id="nlmBridgeStopBtn" data-tooltip="로컬 백엔드 종료 (NAS 브리지·Astra 기능에는 영향 없음)">NotebookLM 백엔드 종료</button>
|
<button class="hdr-menu-item" id="nlmBridgeStopBtn" data-tooltip="로컬 백엔드 종료 (NAS 브리지·Astra 기능에는 영향 없음)">NotebookLM 백엔드 종료</button>
|
||||||
|
<button class="hdr-menu-item" id="nlmLoginBtn" data-tooltip="구글 세션이 만료돼 리서치가 빈 응답을 주거나 로그인 창이 반복해서 뜰 때 — 브라우저에서 인증을 한 번 다시 합니다">NotebookLM 로그인</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="icon-btn" id="historyBtn" data-tooltip="이전 대화 기록 보기">기록</button>
|
<button class="icon-btn" id="historyBtn" data-tooltip="이전 대화 기록 보기">기록</button>
|
||||||
|
|||||||
@@ -1946,6 +1946,8 @@
|
|||||||
if (nlmBridgeStartBtn) nlmBridgeStartBtn.onclick = () => vscode.postMessage({ type: 'startNotebookLmBridge' });
|
if (nlmBridgeStartBtn) nlmBridgeStartBtn.onclick = () => vscode.postMessage({ type: 'startNotebookLmBridge' });
|
||||||
const nlmBridgeStopBtn = document.getElementById('nlmBridgeStopBtn');
|
const nlmBridgeStopBtn = document.getElementById('nlmBridgeStopBtn');
|
||||||
if (nlmBridgeStopBtn) nlmBridgeStopBtn.onclick = () => vscode.postMessage({ type: 'stopNotebookLmBridge' });
|
if (nlmBridgeStopBtn) nlmBridgeStopBtn.onclick = () => vscode.postMessage({ type: 'stopNotebookLmBridge' });
|
||||||
|
const nlmLoginBtn = document.getElementById('nlmLoginBtn');
|
||||||
|
if (nlmLoginBtn) nlmLoginBtn.onclick = () => vscode.postMessage({ type: 'loginNotebookLm' });
|
||||||
addBrainBtn.onclick = () => vscode.postMessage({ type: 'addBrain' });
|
addBrainBtn.onclick = () => vscode.postMessage({ type: 'addBrain' });
|
||||||
// v2.2.67 — 만약 dropdown 이 'new' 상태로 굳어있어도 수정/삭제는 직전 유효 선택(또는 첫 실제 옵션)
|
// v2.2.67 — 만약 dropdown 이 'new' 상태로 굳어있어도 수정/삭제는 직전 유효 선택(또는 첫 실제 옵션)
|
||||||
// 을 기준으로 동작하도록 폴백. 이전엔 'new' 인 순간 그냥 early-return 해서 "수정 안 됨" 버그 발생.
|
// 을 기준으로 동작하도록 폴백. 이전엔 'new' 인 순간 그냥 early-return 해서 "수정 안 됨" 버그 발생.
|
||||||
|
|||||||
@@ -293,6 +293,11 @@
|
|||||||
"default": "",
|
"default": "",
|
||||||
"markdownDescription": "로컬 Datacollector 프로젝트 폴더 경로 (도구 메뉴의 'NotebookLM 백엔드 실행' 버튼이 여기서 `npm run bridge` 를 실행). 비워두면 워크스페이스 주변에서 자동 탐색하고, 못 찾으면 버튼 클릭 시 폴더 선택 창이 떠서 자동 저장됩니다."
|
"markdownDescription": "로컬 Datacollector 프로젝트 폴더 경로 (도구 메뉴의 'NotebookLM 백엔드 실행' 버튼이 여기서 `npm run bridge` 를 실행). 비워두면 워크스페이스 주변에서 자동 탐색하고, 못 찾으면 버튼 클릭 시 폴더 선택 창이 떠서 자동 저장됩니다."
|
||||||
},
|
},
|
||||||
|
"g1nation.notebookLmCliPath": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"markdownDescription": "NotebookLM CLI(`nlm`) 실행 파일 경로 (도구 메뉴의 'NotebookLM 로그인' 버튼이 여기에 `login` 인자를 붙여 실행). 비워두면 파이썬 Scripts 폴더들에서 자동 탐색하고(버전이 올라가도 찾음), 못 찾으면 버튼 클릭 시 파일 선택 창이 떠서 자동 저장됩니다."
|
||||||
|
},
|
||||||
"g1nation.datacollectSavePath": {
|
"g1nation.datacollectSavePath": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "",
|
"default": "",
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { execShell } from '../../lib/execUtil';
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const TERMINAL_NAME = 'NotebookLM Bridge';
|
const TERMINAL_NAME = 'NotebookLM Bridge';
|
||||||
|
const LOGIN_TERMINAL_NAME = 'NotebookLM Login';
|
||||||
const BRIDGE_PORT = 3002;
|
const BRIDGE_PORT = 3002;
|
||||||
/** 자동 탐색 시 찾는 프로젝트 폴더 이름 후보 (맥/윈도우 클론 이름이 다를 수 있음). */
|
/** 자동 탐색 시 찾는 프로젝트 폴더 이름 후보 (맥/윈도우 클론 이름이 다를 수 있음). */
|
||||||
const PROJECT_DIR_NAMES = ['Datacollector_MAC', 'Datacollector', 'Datacollect'];
|
const PROJECT_DIR_NAMES = ['Datacollector_MAC', 'Datacollector', 'Datacollect'];
|
||||||
@@ -55,6 +56,71 @@ export function buildPortKillCommand(platform: NodeJS.Platform, port: number): s
|
|||||||
return `lsof -ti :${port} | xargs kill 2>/dev/null`;
|
return `lsof -ti :${port} | xargs kill 2>/dev/null`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `nlm` 실행 파일 후보 경로를 우선순위대로 나열한다 (존재 검증 안 함 — 순수 함수).
|
||||||
|
*
|
||||||
|
* nlm 은 pip 로 설치되는 콘솔 스크립트라 PATH 에 없는 경우가 흔하다(실측: 이 PC 도 PATH 에
|
||||||
|
* 없고 %APPDATA%\Python\Python314\Scripts 에만 있었다). 파이썬 버전이 올라가면 경로가
|
||||||
|
* 바뀌므로 특정 버전을 하드코딩하지 않고 Scripts 디렉터리들을 훑는다.
|
||||||
|
* 마지막 후보로 맨 이름('nlm')을 두어 PATH 에 있는 환경에서는 그대로 동작하게 한다.
|
||||||
|
*/
|
||||||
|
export function candidateNlmPaths(
|
||||||
|
platform: NodeJS.Platform,
|
||||||
|
env: Record<string, string | undefined>,
|
||||||
|
pythonScriptDirs: string[] = [],
|
||||||
|
cfgPath = '',
|
||||||
|
): string[] {
|
||||||
|
const out: string[] = [];
|
||||||
|
const push = (p: string) => { if (p && !out.includes(p)) out.push(p); };
|
||||||
|
if (cfgPath && cfgPath.trim()) push(cfgPath.trim());
|
||||||
|
|
||||||
|
if (platform === 'win32') {
|
||||||
|
for (const d of pythonScriptDirs) push(path.join(d, 'nlm.exe'));
|
||||||
|
const local = env.LOCALAPPDATA;
|
||||||
|
if (local) push(path.join(local, 'Programs', 'Python', 'Scripts', 'nlm.exe'));
|
||||||
|
push('nlm.exe');
|
||||||
|
} else {
|
||||||
|
for (const d of pythonScriptDirs) push(path.join(d, 'nlm'));
|
||||||
|
const home = env.HOME;
|
||||||
|
if (home) {
|
||||||
|
push(path.join(home, '.local', 'bin', 'nlm'));
|
||||||
|
push(path.join(home, 'Library', 'Python', 'bin', 'nlm'));
|
||||||
|
}
|
||||||
|
push('/usr/local/bin/nlm');
|
||||||
|
}
|
||||||
|
push('nlm'); // PATH 폴백
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 파이썬 사용자 설치 경로 아래의 Scripts(win) / bin(mac) 디렉터리들을 나열한다. */
|
||||||
|
function pythonScriptDirs(): string[] {
|
||||||
|
const dirs: string[] = [];
|
||||||
|
const roots = process.platform === 'win32'
|
||||||
|
? [process.env.APPDATA ? path.join(process.env.APPDATA, 'Python') : '']
|
||||||
|
: [process.env.HOME ? path.join(process.env.HOME, 'Library', 'Python') : ''];
|
||||||
|
for (const root of roots.filter(Boolean)) {
|
||||||
|
try {
|
||||||
|
for (const name of fs.readdirSync(root)) {
|
||||||
|
const s = path.join(root, name, 'Scripts');
|
||||||
|
const b = path.join(root, name, 'bin');
|
||||||
|
if (fs.existsSync(s)) dirs.push(s);
|
||||||
|
if (fs.existsSync(b)) dirs.push(b);
|
||||||
|
}
|
||||||
|
} catch { /* 접근 불가 — skip */ }
|
||||||
|
}
|
||||||
|
return dirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 실제 존재하는 nlm 실행 파일 경로. 못 찾으면 null (PATH 폴백은 호출측이 판단). */
|
||||||
|
function findNlmExecutable(): string | null {
|
||||||
|
const cfg = vscode.workspace.getConfiguration('g1nation').get<string>('notebookLmCliPath', '') || '';
|
||||||
|
for (const cand of candidateNlmPaths(process.platform, process.env, pythonScriptDirs(), cfg)) {
|
||||||
|
if (!path.isAbsolute(cand)) continue; // PATH 이름은 존재 검증 대상이 아님
|
||||||
|
try { if (fs.existsSync(cand)) return cand; } catch { /* skip */ }
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function findBridgeProject(): string | null {
|
function findBridgeProject(): string | null {
|
||||||
const cfgPath = vscode.workspace.getConfiguration('g1nation').get<string>('datacollectLocalProjectPath', '') || '';
|
const cfgPath = vscode.workspace.getConfiguration('g1nation').get<string>('datacollectLocalProjectPath', '') || '';
|
||||||
const wsDirs = (vscode.workspace.workspaceFolders || [])
|
const wsDirs = (vscode.workspace.workspaceFolders || [])
|
||||||
@@ -141,6 +207,48 @@ export async function stopNotebookLmBridge(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NotebookLM 재로그인(구글 인증) 원클릭.
|
||||||
|
*
|
||||||
|
* 배경: NotebookLM 토큰은 ~90분에 만료되고, 전용 프로필의 구글 **세션 자체**가 끊기면
|
||||||
|
* 자동 갱신(Datacollect 의 스케줄 작업)이 매번 실패하면서 로그인 창만 반복해서 띄운다.
|
||||||
|
* 복구는 `nlm login` 을 한 번 사람이 실행해 브라우저에서 인증하는 것뿐인데, 그러려면
|
||||||
|
* 터미널과 실행 파일 경로를 알아야 했다 — 그 한 단계를 버튼으로 없앤다.
|
||||||
|
*
|
||||||
|
* 구현: 셸을 거치지 않고 실행 파일을 터미널 프로세스로 직접 띄운다(shellPath/shellArgs).
|
||||||
|
* 경로에 공백이 있어도 따옴표 처리가 필요 없고, PowerShell/cmd/zsh 차이도 사라진다.
|
||||||
|
* 인증 결과(성공/실패)는 터미널에 그대로 보이므로 별도 폴링을 하지 않는다.
|
||||||
|
*/
|
||||||
|
export async function loginNotebookLm(): Promise<void> {
|
||||||
|
let exe = findNlmExecutable();
|
||||||
|
if (!exe) {
|
||||||
|
const pick = await vscode.window.showErrorMessage(
|
||||||
|
'NotebookLM CLI(nlm) 실행 파일을 찾지 못했습니다. 파일을 직접 선택하면 설정에 저장되어 다음부터는 버튼만 누르면 됩니다.',
|
||||||
|
'파일 선택'
|
||||||
|
);
|
||||||
|
if (pick !== '파일 선택') return;
|
||||||
|
const chosen = await vscode.window.showOpenDialog({
|
||||||
|
canSelectFiles: true, canSelectFolders: false, canSelectMany: false,
|
||||||
|
openLabel: 'nlm 실행 파일 선택',
|
||||||
|
});
|
||||||
|
const f = chosen?.[0]?.fsPath;
|
||||||
|
if (!f) return;
|
||||||
|
await vscode.workspace.getConfiguration('g1nation')
|
||||||
|
.update('notebookLmCliPath', f, vscode.ConfigurationTarget.Global);
|
||||||
|
exe = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
vscode.window.terminals.find(t => t.name === LOGIN_TERMINAL_NAME)?.dispose();
|
||||||
|
const terminal = vscode.window.createTerminal({
|
||||||
|
name: LOGIN_TERMINAL_NAME, shellPath: exe, shellArgs: ['login'],
|
||||||
|
});
|
||||||
|
terminal.show(true);
|
||||||
|
vscode.window.showInformationMessage(
|
||||||
|
'NotebookLM 로그인을 시작했습니다 — 브라우저에서 구글 인증을 완료하세요. ' +
|
||||||
|
'결과는 "NotebookLM Login" 터미널에 표시됩니다.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** 사이드바 웹뷰 메시지 핸들러 — sidebarProvider 의 핸들러 체인에 등록된다. */
|
/** 사이드바 웹뷰 메시지 핸들러 — sidebarProvider 의 핸들러 체인에 등록된다. */
|
||||||
export async function handleBridgeLauncherMessage(_provider: unknown, data: any): Promise<boolean> {
|
export async function handleBridgeLauncherMessage(_provider: unknown, data: any): Promise<boolean> {
|
||||||
switch (data?.type) {
|
switch (data?.type) {
|
||||||
@@ -150,6 +258,9 @@ export async function handleBridgeLauncherMessage(_provider: unknown, data: any)
|
|||||||
case 'stopNotebookLmBridge':
|
case 'stopNotebookLmBridge':
|
||||||
await stopNotebookLmBridge();
|
await stopNotebookLmBridge();
|
||||||
return true;
|
return true;
|
||||||
|
case 'loginNotebookLm':
|
||||||
|
await loginNotebookLm();
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* (터미널 생성·프로세스 kill 같은 부수효과는 VS Code/OS 의존이라 여기선 순수 함수만.)
|
* (터미널 생성·프로세스 kill 같은 부수효과는 VS Code/OS 의존이라 여기선 순수 함수만.)
|
||||||
*/
|
*/
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { candidateProjectDirs, buildPortKillCommand } from '../src/features/datacollect/bridgeLauncher';
|
import { candidateProjectDirs, buildPortKillCommand, candidateNlmPaths } from '../src/features/datacollect/bridgeLauncher';
|
||||||
|
|
||||||
jest.mock('vscode', () => ({
|
jest.mock('vscode', () => ({
|
||||||
workspace: { getConfiguration: jest.fn(), workspaceFolders: [] },
|
workspace: { getConfiguration: jest.fn(), workspaceFolders: [] },
|
||||||
@@ -36,6 +36,39 @@ describe('candidateProjectDirs — 프로젝트 자동 탐색 후보 순서', ()
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('candidateNlmPaths — nlm 실행 파일 탐색 순서', () => {
|
||||||
|
it('설정값이 있으면 항상 1순위다', () => {
|
||||||
|
const p = candidateNlmPaths('win32', {}, ['C:/py/Scripts'], 'D:/custom/nlm.exe');
|
||||||
|
expect(p[0]).toBe('D:/custom/nlm.exe');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('윈도우는 파이썬 Scripts 폴더의 nlm.exe 를 찾는다 (버전 하드코딩 없이)', () => {
|
||||||
|
const p = candidateNlmPaths('win32', {}, ['C:/AppData/Python/Python314/Scripts']);
|
||||||
|
expect(p).toContain(path.join('C:/AppData/Python/Python314/Scripts', 'nlm.exe'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('맥/리눅스는 확장자 없는 nlm 과 ~/.local/bin 을 본다', () => {
|
||||||
|
const p = candidateNlmPaths('darwin', { HOME: '/Users/me' }, ['/Users/me/Library/Python/3.11/bin']);
|
||||||
|
expect(p).toContain(path.join('/Users/me/Library/Python/3.11/bin', 'nlm'));
|
||||||
|
expect(p).toContain(path.join('/Users/me', '.local', 'bin', 'nlm'));
|
||||||
|
expect(p.some(x => x.endsWith('nlm.exe'))).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('마지막 후보는 PATH 폴백용 맨 이름이다', () => {
|
||||||
|
expect(candidateNlmPaths('win32', {}, []).at(-1)).toBe('nlm');
|
||||||
|
expect(candidateNlmPaths('linux', {}, []).at(-1)).toBe('nlm');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('중복 경로는 한 번만 나온다', () => {
|
||||||
|
const p = candidateNlmPaths('win32', {}, ['C:/s', 'C:/s']);
|
||||||
|
expect(p.filter(x => x === path.join('C:/s', 'nlm.exe')).length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('설정값 공백은 무시한다', () => {
|
||||||
|
expect(candidateNlmPaths('win32', {}, [], ' ')[0]).not.toBe(' ');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('buildPortKillCommand — OS 자동 감지 분기', () => {
|
describe('buildPortKillCommand — OS 자동 감지 분기', () => {
|
||||||
it('윈도우는 PowerShell Stop-Process 를 쓴다', () => {
|
it('윈도우는 PowerShell Stop-Process 를 쓴다', () => {
|
||||||
const cmd = buildPortKillCommand('win32', 3002);
|
const cmd = buildPortKillCommand('win32', 3002);
|
||||||
|
|||||||
Reference in New Issue
Block a user