# DeskAide 배포 런북 — 로컬 per-user 모델

각 사용자가 **자기 PC에서 DeskAide를 돌리고, 자기 Claude 로그인(구독)으로** 에이전트를 실행한다. 과금·rate limit이 각자 구독에 걸리고, 토큰을 중앙에 모으지 않아 가장 안전하다. 3개 웹앱(ControlTower·EquipMan·ReportLLM)은 중앙(Tailscale)에 그대로 있고, 그 안의 위젯이 **`http://127.0.0.1:8910`(각 접속자 본인 로컬 DeskAide)**를 가리킨다.

```
사용자 브라우저 ── 중앙 웹앱(Tailscale) 페이지 로드
      │  페이지의 위젯 스크립트 src = http://127.0.0.1:8910/embed.js
      └────────────► 자기 PC의 로컬 DeskAide(127.0.0.1:8910)
                          ├─ claude -p (이 PC 로그인 = 본인 구독)
                          ├─ ToolGateway  (중앙 · TOOLGATEWAY_URL)  ← 프로그램 65종·기능 119개
                          └─ RAG          (중앙 · GEOVIEW_RAG_URL)  ← 사내 문서 벡터 색인
```

**★어디까지 각자 PC 인지** — 헷갈리면 도구가 통째로 죽는다(실측 2026-07-30).

| 구성 | 위치 | 이유 |
|---|---|---|
| claude CLI + 로그인 | **각자 PC** | 요금·rate limit 을 각자 구독에 건다. 이게 이 모델의 목적 |
| DeskAide (8910) | **각자 PC** | 위젯이 `127.0.0.1:8910` 을 가리킨다 |
| ToolGateway (8920) | **중앙 1대** | 감싸는 프로그램·`offsets.db`·사업 대장이 서버·NAS 에 있다 |
| RAG (8901) | **중앙 1대** | 벡터 색인이 크고 야간 배치로 중앙에서 갱신된다 |

그래서 각 직원 `deskaide.env` 에 **`TOOLGATEWAY_URL` 을 반드시 중앙 주소로** 적어야 한다.
비워 두면 자기 PC 의 `127.0.0.1:8920` 을 보는데 거기엔 아무것도 없고, **오류가 아니라 조용히
빈 결과**가 된다(지도가 사업 0건·`available=false`). 서버 쪽도 `TOOLGATEWAY_HOST=0.0.0.0` 으로
열고 방화벽에서 8920 을 사내 대역으로만 허용한다.

`GEOVIEW_ADMIN_KEY` 는 **관리자 PC 에만** 둔다. 직원 env 에 넣으면 그 사람이 owner 전용
기능까지 실행한다 — 그래서 배포되는 `deskaide.env.example` 에는 항목 자체가 없다.

> **비개발자 사용자**: 이 기술 런북 대신 `deploy/사용가이드.md`를 보고, `deploy/DeskAide_설치.bat`를 **더블클릭**하면 끝(Claude 로그인 확인·env 생성·자동시작 등록·구동 자동). 아래는 상세 참조.

## 1. 각 사용자 PC 세팅 (1회)

전제: 그 사용자에게 **Claude 팀플랜 좌석**이 있고 초대를 수락했을 것 + Claude Code(`claude` CLI) 설치.

★좌석이 없거나 초대를 수락하지 않았으면 설치해도 에이전트가 안 돈다(`claude -p` 가 로그인 계정으로 실행되므로). 즉 **배포 대상은 인원수가 아니라 좌석 수**다. 2026-08-04 기준 16석(활성 13·대기 3).

★★플랜 귀속 계약(2026-08-05). 에이전트 토큰이 소비되는 모든 경로에서 **각자의 팀 좌석**을 쓴다. 지키는 방법은 셋이다.

| 무엇 | 어디 | 왜 |
|---|---|---|
| 자격 환경변수를 자식에게 안 넘긴다 | `core/engines.py` `_CREDENTIAL_OVERRIDES` | `ANTHROPIC_API_KEY`·`ANTHROPIC_AUTH_TOKEN`·`CLAUDE_CODE_OAUTH_TOKEN` 이 호스트에 하나만 남아 있어도 그 PC 의 모든 질문이 남의 계정으로 청구된다. 화면에는 표시가 안 나고 청구서에서만 드러난다. **신원 분기보다 위**에서 뗀다 — 아래로 내리면 신원 없는 요청에서만 새는데 그게 가장 흔한 경로다. |
| 어느 계정인지 진단이 말한다 | `core/selftest.py` `check_plan_account()` | 종전 진단은 로그인 **파일이 있는지**만 봤다. 개인 구독으로 로그인해도 초록불이었다. 이제 `organizationType == claude_team` 을 확인해 팀 좌석이면 ok, 아닌 것이 확인되면 fail, 못 읽었으면 unknown. |
| 좌석을 안 쓰는 배치를 알린다 | 같은 검사 | 엔진이 `codex`/`gemini` 면 그 PC 의 ChatGPT·Google 구독을 쓴다. `CLAUDE_CODE_USE_BEDROCK`·`CLAUDE_CODE_USE_VERTEX`·`ANTHROPIC_BASE_URL` 은 공급자 자체를 바꾼다. 둘 다 **조용히 되돌리지 않고** 진단에 fail 로 띄운다(의도한 배치일 수 있으므로 판단은 사람이 한다). |

회귀는 `tests/test_plan_attribution.py`. 새 실행 지점을 추가하면 `test_every_agent_spawn_goes_through_child_env` 가 잡는다 — 헬퍼만 고쳐 두면 새 `subprocess` 호출이 옛 환경으로 조용히 돈다.

**원클릭(권장):** `deploy\DeskAide_설치.bat` 더블클릭 → `setup_deskaide.ps1`이 Python·claude 확인, `deskaide.env` 생성, 로그인 시 자동시작(스케줄 작업 `DeskAide`) 등록, 지금 구동까지 한다. 아래 수동 절차는 그 내부 동작과 동일.

★**Python 을 새로 깔거나 옮기면 설치기를 다시 실행한다**(2026-08-05 신설). 창을 없애면서 작업의 동작이 `powershell`(어디에나 있다)에서 **그 PC 의 `pythonw.exe` 절대 경로**로 바뀌었다. 얻은 것은 확실한 창 억제이고, 대신 그 경로가 유효해야 한다 — 예를 들어 Python 3.13 을 3.14 로 갈아 끼우면 작업은 그대로 옛 경로를 부르고, 그러면 **아무것도 안 뜨는데 오류도 안 보인다**(창이 없으므로). 증상은 "챗봇이 안 보임"뿐이다. 설치기는 몇 번을 돌려도 안전하고(멱등), 다시 돌리면 경로를 새로 기록한다. 확인은 `schtasks /Query /TN DeskAide /XML` 의 `<Command>` 가 실제 존재하는 파일인지 보면 된다.

1. DeskAide 폴더를 그 PC로 복사(예: 공유 폴더/`E:\Software\Reporting\DeskAide` 배포본). Python 3(표준 라이브러리만·별도 pip 불요)·`claude` CLI 필요.
2. Claude 로그인(자기 계정): `claude /login` (또는 `claude setup-token`). 이게 그 사용자 구독을 쓰게 하는 핵심.
3. `deploy/deskaide.env.example`를 `deploy/deskaide.env`로 복사하고 **그 사람 몫의 `TOOLGATEWAY_TOKEN`** 을 채운다(발급은 §1-2). 사내 RAG를 쓰려면 `DESKAIDE_RAG_TOKEN`도 채운다.

   ★2026-08-04 변경: 종전 문구는 "로컬은 토큰 불요(비워둠)"였는데 지금은 틀리다. 게이트웨이가 루프백 밖 호출에 토큰을 요구하므로, 토큰이 없으면 그 사람은 **사내 프로그램 실행·장비 조회를 통째로 못 쓴다.**
4. 구동: `powershell -ExecutionPolicy Bypass -File deploy\start_deskaide.ps1`
   - `127.0.0.1:8910` 바인드, 워커 2, 토큰 없음. 콘솔에 `claude CLI ... 이 PC 로그인 계정의 구독으로 실행` 확인.
5. 상시화(권장): 이 스크립트를 **로그인 시 자동 실행** 스케줄 작업으로 등록. 재시작 시 진행중 잔재는 자동 sweep.

## 1-2. 접속 토큰 발급 (사람마다 하나)

게이트웨이는 **토큰에서 신원을 정한다**(`ToolGateway/core/tokens.py`). 사람마다 따로 내고, 그 값을 그 사람 `deskaide.env` 에 넣는다.

```
cd E:\Software\Utilities\ToolGateway
python -m tools.issue_token --actor kimtm95 --note "김태민 · 2026-08-04"
python -m tools.issue_token --list          # 명단 확인(토큰 값은 안 찍힌다)
```

- `--actor` 는 **그 사람의 지오 계정 id 와 같게** 쓴다. 다르면 원장의 "누가 했나"가 안 이어지고, 본인이 올린 비공개 지도 레이어도 자기에게 안 보인다(가시성 판정이 `owner == actor`).
- 전원 **member** 다. `owner` 는 `--allow-owner` 를 같이 줘야 나오고, 관리자 한 사람으로 족하다 — 받은 사람이 전원의 비공개 자료를 본다.
- 유출·퇴사 시 `--rotate` 로 교체하면 옛 토큰이 즉시 무효가 된다.
- ★**같은 토큰을 여러 명에게 복사하지 마라.** 원장이 섞이고, 등급이 섞이면 경계가 통째로 무너진다.

**적용 순서: 게이트웨이 → DeskAide.** 먼저 발급해 대장(`user_tokens.json`)에 올리고, 그 다음에 그 사람 `deskaide.env` 에 넣고 그 사람 DeskAide 를 재기동한다. 반대로 하면 그 사람 챗봇이 **401 을 먹는다** — 예전에 공용 토큰을 양쪽에 맞추기 전에 재기동해서 실제로 겪었고, 원인이 env 에도 코드에도 안 보여 추적이 어려웠다. 대장은 mtime 캐시라 **게이트웨이 재기동은 필요 없다**(발급 즉시 반영).

## 1-3. 게이트웨이 개방 (중앙 1대·한 번만)

회원 PC 가 중앙 게이트웨이를 부르려면 루프백만 듣고 있으면 안 된다. `gateway.env` 에 `TOOLGATEWAY_HOST=0.0.0.0` (2026-08-04 적용).

- 자격은 토큰이 판정한다 — 루프백 밖 요청은 토큰이 없으면 401(실측).
- Tailscale **Funnel** 은 10000·443·8443 만 공개하고 8920 은 포함하지 않는다(실측). 즉 사내(tailnet) 개방이지 인터넷 공개가 아니다.

## 2. 중앙 웹앱 (한 번만·이미 배선됨)

3앱의 위젯 origin 기본값이 이미 `http://127.0.0.1:8910`이라 **추가 env 불요**(모든 사용자에게 동일한 로컬 주소). 코드 배선은 완료(각 앱 커밋). 앱 서비스가 위젯 스크립트를 렌더하도록 재기동만 하면 된다(ControlTower는 `next build` 후 재기동·정적 프리렌더라 origin 이미 bake).

- 위젯은 각 앱의 로그인 사용자에게만 렌더된다(ControlTower=전역·EquipMan=`session.actor_id`·ReportLLM=실로그인). 그 사용자 PC에 로컬 DeskAide가 떠 있으면 위젯이 뜨고, 아니면 안 뜬다(호스트 무영향).

- ★**위젯은 웹앱을 그 PC에서 직접 열 때만 뜬다**(실측 2026-08-03). 웹앱 주소가 `127.0.0.1`/`localhost`가 아니면 위젯 자리에 "지오는 각자 PC 에서 돕니다" 안내가 대신 뜬다. 두 경우 다 그렇다 - Tailscale 같은 https 주소로 열면 `http://127.0.0.1:8910/embed.js`가 브라우저의 mixed content 차단에 걸려 스크립트가 실행조차 안 되고, LAN IP로 다른 PC에서 열면 그 PC의 127.0.0.1에는 지오가 없다.

  EquipMan은 중앙 1대(pc-071:5405)에서 돌므로 **대부분의 직원은 EquipMan 화면 안에서는 위젯을 못 쓴다.** 그건 설계대로다 - 질문이 그 PC에 로그인된 Claude 계정으로 나가므로, 중앙에서 띄우면 요금과 rate limit이 그 한 계정에 몰린다. 직원은 자기 PC의 `http://127.0.0.1:8910`을 **직접 열어** 쓴다(즐겨찾기·바로가기). 장비·문서·지도는 지오 안에서 다 된다.

  ★트레이 아이콘(`deploy/tray.pyw`)은 **선택 사항**이다(2026-08-05 정정). 상시 기동은 스케줄 작업 `DeskAide`·`ToolGateway`가 맡고, 트레이는 상태 표시와 열기·재시작 편의일 뿐이다. 그리고 이것만 표준 라이브러리가 아니다 — `pip install pystray pillow`가 필요하다. 종전 문구는 이 사실 없이 "트레이 아이콘"으로 쓰라고 안내했고, 그대로 따르면 새 PC에서는 창 없는 `.pyw`가 **아무 표시도 없이** 죽는다. 지금은 무엇을 깔면 되는지 메시지 상자로 알리고 나간다.

## 3. 검증 (사용자 PC에서)

- `start_deskaide.ps1` 콘솔: `token=none(local)`·`claude CLI ...` 출력.
- **`http://127.0.0.1:8910`을 연다.** 첫 화면 아래에 안 붙은 항목이 있으면 그 자리에 뜬다 - 다 붙었으면 아무것도 안 보인다(정상이 침묵). 항목별로 사유와 조치가 한 줄씩 나온다.
  - 같은 내용을 `http://127.0.0.1:8910/api/selftest`로도 볼 수 있다(설치 지원할 때 화면 없이 확인).
- 첫 계정: 계정이 하나도 없으면 첫 설정 화면이 뜬다. **직원 PC에서 만든 첫 계정은 member 다**(관리자 키가 없으므로). 두 번째 계정은 이 문으로 못 만든다.
- 질의 → "잠시만요" → 결과. 이 결과는 **본인 구독**으로 실행된 것(사용량은 본인 Claude 플랜에 잡힘).
- 웹앱 임베드까지 보려면 그 웹앱을 **그 PC에서** `127.0.0.1` 주소로 열어야 한다(§2 참조).

★배포본 실측(2026-08-03): zip 143파일을 개발 트리 밖에 풀고 관리자 키·토큰 없이 띄워 위 흐름을 그대로 재현했다. 첫 계정 member · 두 번째 계정 403 · 관리자 라우트 403 · 자가진단이 빠진 항목 3가지를 지목.

## 옵션 — 팀 중앙 공유 모델 (참고)

각자 구독이 아니라 한 계정을 공유해도 되면(비용·rate limit이 그 한 계정에 집중), `deskaide.env`에 `DESKAIDE_HOST=0.0.0.0`+`DESKAIDE_TASK_TOKEN` 설정하고 3앱 origin을 Tailscale 주소로 env 오버라이드하면 된다(중앙 1대 구동). 단 그 계정 하나가 전 팀 사용량을 감당하고, rate limit이 실질 천장이 된다. 기본은 로컬 per-user(위)를 권장.

- ★**첫 계정은 그 PC 에서 만들어야 한다**(2026-08-03). 공유 모드에서 LAN IP로 붙어 재현했더니, 계정이 아직 없는 PC를 `/api/whoami`의 `first_run`으로 찾아 `/api/setup`으로 **남이 선점**할 수 있었다. 그 뒤로 그 PC의 지오는 선점자 것이 되고 비용은 PC 주인의 Claude 계정에서 나간다. `DESKAIDE_TASK_TOKEN`을 설정해도 이 문은 안 막혔다 - 지금은 루프백에서만 열린다(원격 403).
- ★자가진단(`/api/selftest`)은 원격 호출에 **상태만** 답하고 상세는 뺀다. "자산 1,535건"·"서버에 닿음" 같은 값은 사내에 무엇이 얼마나 있는지를 말한다. 설치 지원은 그 PC에서 연다.

## 프로필별 데이터 접근 (로컬 모델 주의)

- **general·docs** (RAG+웹): 로컬 DeskAide가 중앙 RAG 서버(Tailscale `GEOVIEW_RAG_URL`)를 호출 + 웹검색. `DESKAIDE_RAG_TOKEN`만 있으면 각 PC에서 동작. RAG MCP 브릿지는 `core/rag_mcp.py`로 **번들**돼 있어(ReportLLM 미동봉 PC도 OK) 폴더째 복사만으로 붙는다.
- **equipman** (장비 조회): ★**2026-08-04 변경 — 공유 폴더가 더 이상 필요 없다.** 장비 조회를 게이트웨이 기능(`equipman.find`·`equipman.summary`)으로 올렸고, 대장이 있는 중앙 서버에서 읽기 전용으로 연다. 각 사용자 PC 는 `TOOLGATEWAY_TOKEN` 만 있으면 되고 `EQUIPMAN_DB` 는 설정하지 않는다.

  종전 문구는 "`EQUIPMAN_DB` 를 UNC 공유로 지정해야 한다"였는데 두 가지로 틀렸다. 첫째, 이 PC 에 **SMB 공유가 하나도 열려 있지 않다**(실측: `ADMIN$` 뿐) — 안내하던 `\\pc-071\EquipMan\...` 는 존재하지 않는 경로다. 둘째, 파일을 통째로 여는 것보다 게이트웨이 경유가 낫다: 자격증명을 나눠 줄 일이 없고 **누가 무엇을 물었는지 원장에 남는다.**

  중앙 서버(대장이 있는 PC)에서는 종전대로 로컬 파일을 직접 읽는다. `deskaide.env` 에 `EQUIPMAN_DB` 가 없으면 `equipman_mcp._DEFAULT_DB` 를 쓰고, 그 파일이 없으면 도구를 붙이지 않고 **degraded 로 밝힌다**(조용히 빈 답을 내지 않는다).

  ★UNC 경로가 실제로 되는지 실측했다(2026-08-03). `equipman.db`는 WAL 모드라 네트워크 경로에서 막힐 수 있는데, SMB 경로에서 `mode=ro`로 열면 **최신 변경까지 읽힌다.** 현재 코드의 연결 방식(`"file:" + path + "?mode=ro"`)이 UNC 경로를 그대로 받는다 - 백슬래시도 슬래시 개수도 문제가 안 된다.

  ★단 `immutable=1`은 절대 붙이면 안 된다. WAL을 무시하므로 **방금 등록한 자산이 안 보인다**(실측: 새 테이블 자체를 "없다"고 답한다). "네트워크에서 안 열린다"며 이 옵션을 붙이면 낡은 대장을 조용히 보게 된다.

## 이식성(다른 PC 호환)

- **부트스트랩 설치(어떤 PC 상태든)**: `DeskAide_설치.bat`(→`setup_deskaide.ps1`)가 **Python·Claude Code를 없으면 자동 설치**(winget→python.org·claude.ai 공식 설치기 `irm https://claude.ai/install.ps1|iex`·전부 사용자 범위·관리자 불요). 64비트 확인·Store 스텁 제외·설치 직후 PATH 세션 갱신(`Sync-Path`). 찾은/설치한 파이썬 경로를 **`DESKAIDE_PYTHON_EXE`**로 `deskaide.env`에 기록 → 서버·MCP가 PATH 상태와 무관하게 그 파이썬으로 구동. **로그인만 사용자 대화형**(브라우저 OAuth·스크립트 불가 — 설치기가 마지막에 로그인 창 유도).
- MCP 서브프로세스는 **실행 중 파이썬(`sys.executable`)**으로 띄운다 — 특정 파이썬 경로(예: Python313)를 가정하지 않아 어느 PC서든 존재·호환. 필요 시 `DESKAIDE_PY`로 오버라이드.
- MCP 스크립트(`rag_mcp.py`·`equipman_mcp.py`)는 `core/`에 **상대 경로**로 참조 — 설치 폴더 위치(C:\DeskAide 등) 무관.
- 배포 스크립트(`*.ps1`)는 **스케줄 작업이 Windows PowerShell 5.1로 실행**한다(pwsh 7 아님). 한글 파싱 안전을 위해 **UTF-8 BOM** 저장 필수(BOM 없이 문자열 안 한글이 cp949 오인→파싱 실패 이력). 스크립트 편집 후 BOM 유지 확인.
- 원격 PC 필수 env: `GEOVIEW_RAG_URL`(중앙 RAG·Tailscale), `DESKAIDE_RAG_TOKEN`(clearance ≤ technical), `TOOLGATEWAY_URL`(중앙 게이트웨이), `TOOLGATEWAY_TOKEN`(그 사람 몫). 장비는 게이트웨이 경유라 `EQUIPMAN_DB` 불요.
- **★게이트웨이 접속 토큰 — 사람마다 다른 값이다(2026-08-04 정정).** 종전 문구는 "`gateway.env`·`deskaide.env` 양쪽에 있고 **값이 같아야 한다**"였는데 지금은 틀리고, 그대로 하면 같은 토큰(최악은 관리자 토큰)을 전원에게 복사하게 된다. 발급은 `tools/issue_token.py`(§1-2)이고 대장은 `ToolGateway/deploy/user_tokens.json` 이다. `gateway.env` 의 `TOOLGATEWAY_TOKEN` 은 **접근 게이트용 공용값**이라 사용자 토큰과 별개다.
  토큰이 없으면 게이트웨이가 모든 호출에 401 을 내고 지오가 사내 프로그램을 통째로 못 쓴다(실측: 그 상태로 15분 돌았고, 원인이 env 에도 코드에도 없어 추적이 어려웠다).
- **문서 재색인 창**: 매일 20:00 부터 약 56분간 문서 서버(8901)가 내려간다(인덱싱 52분 + 기동 4.5분·실측 2026-08-04). 그동안 지오는 문서 검색이 꺼졌다고 답 앞에 먼저 밝힌다. 시각은 `GeoView_RAG_Nightly` 스케줄 작업에서 바꾼다.
  - **팀원 배포에서는 설치기가 한 번 묻는다.** 패키지에 실키를 못 싣기 때문이다(`build_package.ps1` 이 `*.env` 를 빼고, 남아 있으면 빌드를 세운다). 설치 안내 메일에 토큰 값을 적어 보내고, 받는 사람은 `DeskAide_설치.bat` 실행 중 "게이트웨이 접속 토큰" 물음에 붙여 넣으면 끝난다. 엔터로 건너뛰면 대화·문서검색만 되고 사내 프로그램은 안 된다 — 설치기가 그렇게 알린다.
  - **토큰을 안 실은 호출자는 "응답 없음"으로 보인다.** 401 은 `HTTPError` 이고 그건 `URLError` 의 하위 클래스라, `except URLError` 로 뭉뚱그린 코드가 "서버가 죽었다"로 표시한다. 실제로 트레이가 멀쩡한 게이트웨이를 빨간불로 칠했다. 새 호출자를 붙이면 `tests/test_gateway_callers_send_token.py` 목록에 추가한다.
  - ★**2026-08-04 정정 — 이제 토큰이 사람을 가린다.** 종전에는 "전원이 같은 값을 쓰고, 사람 단위 신원은 다른 층(DeskAide 로그인 → `GEOVIEW_ACTOR`)에 있다"고 적혀 있었는데 지금은 틀리다. 게이트웨이가 **토큰에서 신원과 등급을 정한다**(`core/tokens.py`) — 사람마다 다른 값이고, 본문 `actor` 는 무시된다. 같은 값을 나눠 주면 원장의 "누가 했나"가 섞이고, 관리자 토큰을 나눠 주면 받은 사람이 전원의 비공개 자료를 본다.
  - 새 PC 설치 시: 관리자가 그 사람 몫을 발급해(§1-2) 전달하고, 받은 사람이 `deskaide.env` 에 넣는다. **패키지에는 안 실린다**(실키 제외).
  - 순서 - 발급해 대장에 올린 뒤 그 사람 `deskaide.env` 에 넣고 그 사람 DeskAide 만 재기동한다(§1-2). 게이트웨이 재기동은 불요(mtime 캐시). 반대로 하면 그 사람 챗봇이 401 을 먹는다.
  - 확인: 토큰 없이 `http://127.0.0.1:8920/health` → 401, 헤더 `X-Geoview-Token` 붙이면 200.
- **★설치 후 각자 자기 문서 계정을 연결한다(2026-07-31 신설).** `DESKAIDE_RAG_TOKEN`은 **공용 서비스 계정**(deskaide·clearance=technical)이라, 그것만 쓰면 계약·제안 경로에 있는 최근 사업 문서가 통째로 안 보인다. 실측: 사업 문서 가시성 8,423/20,830(40%)이고 **2025년 0/4,026 · 2026년 0/1,396** — 옛 사업은 답하고 지금 하는 일은 못 답한다.
  1. 문서 서버(`GEOVIEW_RAG_URL`)에 **본인 계정으로** 로그인 → API 토큰 발급.
  2. DeskAide 위젯 오른쪽 위 계정 버튼 → "문서 서버 토큰"에 붙여 넣고 [연결].
  3. 연결되면 그 자리에 "○○○ 계정으로 연결됨 · <등급> 등급"이 뜬다. 안 하면 미연결 경고가 보인다.
  - 토큰은 `data/rag_links.json`에 사용자별로 저장된다(0600·gitignore). 값은 화면·로그·API 응답 어디에도 안 나온다.
  - 연결이 없으면 종전대로 서비스 계정으로 동작한다(닫힌 쪽이 기본).

## 운영 참고

- **재시작 절차(코드 갱신 반영)**: `schtasks /End /TN DeskAide` → 8910 python 종료 → **3초 대기** → `schtasks /Run /TN DeskAide`. 대기 없이 바로 /Run 하면 스케줄러의 "이미 실행 중이면 무시" 정책에 걸려 조용히 미기동(실측 2회). 서버 자체도 bind 10회 재시도가 있어 포트 잔류는 흡수한다. widget.html은 재시작 불요(매 요청 디스크 read), `core/*.py`는 재시작 필요. **env 는 재시작이 필요하고, 그때 파이썬만 죽이면 안 된다** — 런처(PowerShell)가 기동할 때 `deskaide.env` 를 읽어 자기 프로세스 env 에 넣고 자식 파이썬이 그걸 물려받는다. 파이썬만 죽이면 감시 루프가 **옛 값을 그대로 든 채** 새 파이썬을 띄운다(실측 2026-08-05: 게이트웨이 토큰을 바꾸고 파이썬만 재기동했더니 자가진단이 계속 "인증이 거부됐습니다"였고, 같은 토큰으로 게이트웨이를 직접 부르면 200 이었다). 감시자까지 내리고 `Start-ScheduledTask -TaskName DeskAide` 로 다시 띄운다.
- `jobs.jsonl`·`usage.jsonl` append-only. 대용량화 시 재시작(sweep)·아카이브.
- 워커 수(`DESKAIDE_WORKERS`, 기본 4 · 2026-07-30 에 2 에서 올림) = 동시 `claude -p` 수. per-user 배포라 인스턴스당 사용자는 한 명인데, **혼자 써도 몰아넣는다** — 단일 사용자 잡 이력 221건 실측에서 60초 창 최다 8건이 들어갔고 한 건이 중앙 48초·75% 122초·90% 259초다(quick 45·fast 60·careful 156). 2개면 8번째가 3분을 넘기고 4개면 그 절반이다. 비용은 유휴 시 0(폴링 스레드뿐, 자식은 일 있을 때만) 이고, 동시 3건 실측이 claude 자식 각 370~375 MB + 손자(MCP python×3·bash) 합계 1,324 MB 였다 — 4건이면 1.8 GB 안쪽. 사무용 PC 에서 엑셀·CAD 와 같이 쓰는 상한선으로 본다. 더 올리려면 그 PC 사양 확인 선행. 요금·rate limit 은 각자 구독에 걸린다(0.0.0.0 중앙 공유로 돌릴 때만 호스트 계정 하나로 몰린다 — 예외 경로).
- 기밀: RAG 켜면(local이라도) 사내 문서가 클라우드 claude로 전송(각자 구독 경유). 민감도 낮추려면 특정 프로필만 RAG(`profiles.py`).
- **자동 복구(2026-08-04 추가)**: DeskAide·ToolGateway 작업에 10분 반복 트리거를 건다. 로그온 트리거만 있으면 "상시 기동"이 실제로는 **1회 기동**이다 — 실측으로 두 서비스 다 내려가 있었고(작업 상태 Ready·마지막 결과 0xC000013A) 로그는 "정상 기동"에서 그대로 끊겨 있었다. 런처의 `while ($true)` 는 파이썬만 지키고 감시자 자신이 죽으면 아무도 안 살린다. 반복이 안전한 근거는 런처가 이미 살아 있으면 스스로 빠져나오는 것이고(잠금 파일 + 리스너 확인), 실측으로 확인했다(두 번 실행 → 감시자 1개 유지·포트 유지). 설치기(`setup_deskaide.ps1` 4절)가 이 설정을 함께 등록한다. **관리자 PC 만 손보면 나머지 PC 는 같은 공백이 남는다.**
- **비밀 파일 권한**: `deskaide.env`·`data/accounts.json`·`data/rag_links.json`·`data/approvals/`·(게이트웨이 쪽) `gateway.env`·`user_tokens.json`·`ledger/runs.jsonl` 은 소유자·SYSTEM·Administrators 만 접근하게 잠근다. **쓸 때마다 코드가 다시 건다** — `os.replace` 로 놓인 새 파일은 상위 폴더 ACL 을 상속해서, 손으로 건 권한은 다음 쓰기에 사라진다(실측 2026-08-04: 토큰 발급 14건이 대장 권한을 씻어 냈고, 그 상태에서는 이 PC 의 아무 계정이나 자기 role 을 owner 로 바꿀 수 있었다). `os.chmod(0o600)` 은 Windows 에서 방어가 아니다(읽기전용 속성만 토글).
  - 상태 확인·복구: 게이트웨이 쪽은 `python -m tools.issue_token --secure` (내용 불변·권한만 다시 건다). 게이트웨이는 기동할 때도 스스로 다시 건다(`web/server.py` `lock_own_secrets`).
  - 확인 명령: `icacls <파일>` 결과에 `Authenticated Users`·`Everyone`·`BUILTIN\Users` 가 `(M)`·`(F)`·`(W)` 로 나오면 열린 것이다. **`(M,DC)`·`(RX,W)` 처럼 묶인 표기도 같은 뜻이다** — `(M)` 만 눈으로 찾으면 놓친다.
