- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환 - Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용 - pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관) - worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md) - D:\Rhino-MCP 경로 참조를 새 위치로 치환 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
61 lines
2.6 KiB
Python
61 lines
2.6 KiB
Python
"""Import an owner-authorized ChatGPT login over stdin into a private volume.
|
|
|
|
Never print credentials or replace a refreshed server login with an old copy.
|
|
This script has no HTTP endpoint and is invoked by the server operator only.
|
|
"""
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import sys
|
|
import tempfile
|
|
|
|
LIMIT=1024*1024
|
|
|
|
|
|
def parse_auth(payload):
|
|
if not payload or len(payload)>LIMIT: raise ValueError('Invalid AI authentication file size')
|
|
try: value=json.loads(payload)
|
|
except (ValueError,UnicodeError): raise ValueError('Invalid AI authentication JSON') from None
|
|
if not isinstance(value,dict) or value.get('auth_mode') not in (None,'chatgpt'):
|
|
raise ValueError('ChatGPT authentication is required')
|
|
tokens=value.get('tokens')
|
|
if not isinstance(tokens,dict) or any(not isinstance(tokens.get(k),str) or not tokens[k].strip()
|
|
for k in ('access_token','refresh_token','id_token','account_id')):
|
|
raise ValueError('Incomplete ChatGPT authentication')
|
|
return value
|
|
|
|
|
|
def install(payload, directory):
|
|
incoming=parse_auth(payload); directory=Path(directory)
|
|
if directory.is_symlink(): raise ValueError('AI authentication directory must not be a symlink')
|
|
directory.mkdir(mode=0o700,parents=True,exist_ok=True);directory.chmod(0o700)
|
|
target=directory/'auth.json'
|
|
if target.is_symlink(): raise ValueError('AI authentication file must not be a symlink')
|
|
if target.exists():
|
|
existing=parse_auth(target.read_bytes())
|
|
if existing['tokens']['account_id']!=incoming['tokens']['account_id']:
|
|
raise ValueError('A different server account exists; it was not overwritten')
|
|
target.chmod(0o600)
|
|
return 'Existing server login preserved (including refreshed credentials)'
|
|
descriptor,temporary=tempfile.mkstemp(prefix='.auth-import-',dir=directory)
|
|
try:
|
|
with os.fdopen(descriptor,'wb') as stream:
|
|
stream.write(json.dumps(incoming).encode());stream.flush();os.fsync(stream.fileno())
|
|
try: os.link(temporary,target)
|
|
except FileExistsError: return install(payload,directory)
|
|
finally: Path(temporary).unlink(missing_ok=True)
|
|
return 'ChatGPT login installed in the private server volume'
|
|
|
|
|
|
def main():
|
|
try:
|
|
print(install(sys.stdin.buffer.read(LIMIT+1),Path.home()/'.codex'))
|
|
except (ValueError,OSError) as error:
|
|
# Messages are fixed strings, never JSON, token values or stdin.
|
|
print(str(error) if isinstance(error,ValueError) else 'Cannot install AI authentication',file=sys.stderr)
|
|
return 1
|
|
return 0
|
|
|
|
|
|
if __name__=='__main__':sys.exit(main())
|