`;
}
/** [0.3.1] 서버에서 찾은 CLI 표(/ops/api/ai/cli). 로그인 안 됨은 안내 문구와 함께, 연결 추가 단추는 양식을 미리 채운다. */
export function clisHtml(data) {
const clis = (data && data.clis) || []; const searched = (data && data.searched) || [];
if (!clis.length) return `
서버에서 CLI 를 찾지 못했습니다 — 탐색 위치: ${esc(searched.slice(0, 6).join(', '))}${searched.length > 6 ? ' …' : ''}. 서버(HAL 실행 계정)에 Claude Code 또는 Codex CLI 를 설치하거나 연결 설정에 절대 경로를 적으세요.
${esc(c.defaultModel || '')}${c.models && c.models.length ? ` 사용 가능 ${c.models.length}개` : ''}
`;
return `
CLI
버전
로그인
기본 모델
${clis.map(row).join('')}
`;
}
/** [0.3.2] 사전 준비 안내(공급자·인증 방식별). provider 는 /ops/api/ai/connections 의 providers 항목. */
export function guideHtml(provider, authKind, callbackUrl) {
if (!provider) return '사전 준비 — 공급자를 고르세요.'; const g = provider.guide || {}; const lines = [];
if (authKind === 'api_key') { lines.push(...(g.steps || [])); if (g.keyUrl) lines.push(`키 발급: ${esc(g.keyUrl)}`); lines.push('키는 저장 뒤 Vault 에만 있고 다시 표시하지 않습니다. 저장하면 바로 "확인"(모델 목록)을 진행합니다.'); }
else if (authKind === 'oauth') { if (g.oauth) lines.push(esc(g.oauth)); if (provider.oauthPreset && provider.oauthPreset.mode === 'openrouter_pkce') lines.push('저장하면 OpenRouter 로그인 창이 자동으로 열립니다(팝업 허용). 승인하면 키가 자동 발급되어 Vault 에 저장됩니다.'); else lines.push(`HAL 콜백 주소(공급자의 리디렉션 URI 에 등록): ${esc(callbackUrl || '')}`, '저장하면 공급자 로그인 창이 자동으로 열립니다(팝업이 막히면 같은 창에서 이동).'); }
else { lines.push(...(g.steps || [])); lines.push('저장하면 서버에서 CLI 를 탐색해 로그인 상태를 확인하고, 로그인이 안 되어 있으면 텍스트 셸(login)을 자동으로 엽니다.'); }
return `사전 준비 — ${esc(provider.label)} · ${esc({ api_key: 'API 키', oauth: 'OAuth', login: '로그인(CLI)' }[authKind] || authKind)}${lines.map((l) => `
`;
}
/** [0.3.2] 도구 정책 선택 목록(위험 등급별 체크박스). tools: /ops/api/ai/tools 의 tools, enabled: 현재 켜진 이름 */
export function toolPolicyHtml(tools, enabled) {
const on = new Set(enabled || []); const groups = [['read', '읽기(즉시 실행)'], ['write', '쓰기(승인·재인증)'], ['high', '고위험(승인·재인증·3단계 확인)']];
return groups.map(([risk, label]) => { const list = tools.filter((t) => t.risk === risk); return `
${esc(label)}
${list.map((t) => ``).join('')}
`; }).join('');
}
/** [0.3.9] 모델 선택(확인된 목록에서). 목록이 없으면 "확인" 안내. */
export function modelSelectHtml(c) {
const models = (c && c.models) || []; if (!models.length) return `모델 목록 확인 전 — "확인"을 누르면 고를 수 있습니다`;
const cur = c.model || ''; const opts = [...new Set([cur, ...models].filter(Boolean))];
return ``;
}
/** [0.3.9] 텍스트 셸 세션 목록(살아 있는 것·끝난 것 구분, 열기·종료) */
export function shellSessionsHtml(sessions) {
if (!sessions || !sessions.length) return '열린 텍스트 셸이 없습니다.';
return `