File size: 4,345 Bytes
aa73ff9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
"""Merge portable Laya review hooks into a local Claude settings file."""
import argparse,datetime,json,os,shutil,tempfile
from pathlib import Path
from urllib.parse import urlsplit
HERE=Path(__file__).resolve().parent

def main():
    p=argparse.ArgumentParser();p.add_argument('--claude-dir',type=Path,default=Path.home()/'.claude');p.add_argument('--api-key-env');p.add_argument('--remove-foreign-linux-hooks',action='store_true');a=p.parse_args()
    root=a.claude_dir.expanduser().resolve();settings=root/'settings.json';before=settings.read_bytes();data=json.loads(before)
    base=data.get('env',{}).get('ANTHROPIC_BASE_URL','');u=urlsplit(base)
    if u.scheme not in ('http','https') or not u.hostname or u.username or u.password or u.query or u.fragment:raise SystemExit('Expected existing LiteLLM ANTHROPIC_BASE_URL without embedded credentials')
    folder=root/'laya';folder.mkdir(exist_ok=True,mode=0o700);folder.chmod(0o700)
    for name in ('hook.py','global_hook.py','doctor.py'):shutil.copy2(HERE/name,folder/name)
    shutil.copy2(HERE.parents[1]/'questions.json',folder/'questions.json')
    credential_env=a.api_key_env or next((name for name in ('ANTHROPIC_AUTH_TOKEN','ANTHROPIC_API_KEY','LITELLM_API_KEY') if data.get('env',{}).get(name)),'ANTHROPIC_AUTH_TOKEN')
    import re
    if not re.fullmatch(r'[A-Z_][A-Z0-9_]*',credential_env):raise SystemExit('Invalid credential variable name')
    config={'endpoint':u.scheme+'://'+u.netloc+'/v1/systemone','model':'laya-devops-decisions','api_key_env':credential_env,'environment':'stage','minimum_allow_probability':.9,'timeout_seconds':8,'state_dir':'~/.claude/laya/state','allow_behavior':'keep_native_claude_permissions'}
    # Runtime location remains portable between the Mac and /home/... on the VM.
    if root!=Path.home()/'.claude':config['state_dir']=str(folder/'state')
    target=folder/'config.json'
    if target.exists():
        shutil.copy2(target,folder/('config.backup-'+datetime.datetime.now().strftime('%Y%m%d-%H%M%S-%f')+'.json'))
    target.write_text(json.dumps(config,indent=2)+'\n');target.chmod(0o600)
    command='python3 "$HOME/.claude/laya/global_hook.py" || { echo "Laya hook failed; action blocked." >&2; exit 2; }'
    if root!=Path.home()/'.claude':
        import shlex
        command='python3 '+shlex.quote(str(folder/'global_hook.py'))+' || exit 2'
    hooks=data.setdefault('hooks',{});removed=[]
    if a.remove_foreign_linux_hooks:
        import sys
        if sys.platform!='darwin':raise SystemExit('Linux hook cleanup is only intended for this Mac')
        for event,groups in list(hooks.items()):
            keep=[]
            for group in groups:
                entries=[]
                for h in group.get('hooks',[]):
                    existing_command=h.get('command','')
                    if any('/home/example-user/.claude/'+name in existing_command for name in ('claude-signal.py','claude-signal-reply.py','notify.js')):removed.append({'event':event,'command':existing_command})
                    else:entries.append(h)
                if entries:keep.append({**group,'hooks':entries})
            if keep:hooks[event]=keep
            else:hooks.pop(event)
    for event,matcher in [('UserPromptSubmit',None),('PreToolUse','Bash')]:
        groups=hooks.setdefault(event,[])
        if any('laya/global_hook.py' in h.get('command','') for g in groups for h in g.get('hooks',[])):continue
        group={'hooks':[{'type':'command','command':command,'timeout':15}]}
        if matcher:group['matcher']=matcher
        groups.append(group)
    if settings.read_bytes()!=before:raise SystemExit('Settings changed concurrently; no overwrite')
    backup=root/('settings.backup-before-laya-'+datetime.datetime.now().strftime('%Y%m%d-%H%M%S-%f')+'.json')
    fd=os.open(backup,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600)
    with os.fdopen(fd,'wb') as f:f.write(before)
    fd,temp=tempfile.mkstemp(dir=root,prefix='.settings-laya-',suffix='.tmp')
    with os.fdopen(fd,'w') as f:json.dump(data,f,ensure_ascii=False,indent=2);f.write('\n')
    os.replace(temp,settings)
    print(json.dumps({'settings':str(settings),'backup':str(backup),'runtime':str(folder),'endpoint':config['endpoint'],'model':config['model'],'credentials_copied':False,'removed_foreign_linux_hooks':len(removed)}))

if __name__=='__main__':main()