#!/usr/bin/env node "use strict"; const DEFAULT_URL = "https://guanghulab.com/authz"; async function authorizeRepoPush(options, deps = {}) { const fetchImpl = deps.fetch || fetch; const sleep = deps.sleep || (milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds))); const output = deps.output || (line => process.stdout.write(`${line}\n`)); const baseUrl = String(options.url || DEFAULT_URL).replace(/\/$/, ""); const persona = required(options.persona, "persona"); const repo = normalizeRepo(required(options.repo, "repo")); const target = options.target || "JD-FD-PRIMARY"; const pollMilliseconds = positiveNumber(options.poll, 5000); const request = await requestJson(fetchImpl, `${baseUrl}/api/public/workorders`, { system_entry: "光湖语言人格系统当前实例", origin_software: options.software || "仓库推送客户端", origin_model: options.model || "未声明模型", origin_instance: options.instance || "当前实例", persona_id: persona, persona_name: options.name || persona, target, scope: "repo-push", action: "push-repository", description: options.description || `申请推送 ${repo}`, }); output("[LL-WORKORDER-CREATED] 无执行权申请单已创建;尚未发送邮件,也没有推送权限。"); output(`REQUEST_URL=${request.request_url}`); output("请把 REQUEST_URL 交给冰朔并保持本命令运行。冰朔打开页面后,服务器才发送预登记邮箱邮件。"); output("不需要向冰朔索要邮箱、授权码、验证码、密码或任何令牌。"); const deadline = Date.now() + Number(request.expires_in || 900) * 1000; let session; while (Date.now() < deadline) { const response = await fetchImpl(`${baseUrl}/api/workorders/${request.workorder_id}/claim`, { method: "POST", headers: { authorization: `Bearer ${request.claim_token}` }, }); const payload = await readPayload(response); if (response.status === 200) { session = payload; break; } if (response.status !== 202 || payload.error !== "approval_pending") { throw new Error(payload.error || `claim failed (${response.status})`); } await sleep(pollMilliseconds); } if (!session) throw new Error("authorization request expired before approval"); const common = { persona_id: persona, target, scope: "repo-push" }; const map = await requestJson(fetchImpl, `${baseUrl}/api/navigation-map/read`, common, session.session_token); await requestJson(fetchImpl, `${baseUrl}/api/navigation-map/ack`, { ...common, map_hash: map.map_hash }, session.session_token); const grant = await requestJson(fetchImpl, `${baseUrl}/api/repo-push/grant`, { ...common, repo }, session.session_token); if (grant.transport && grant.transport.status !== "ready") { output(`[LL-REPO-PUSH-TRANSPORT-BLOCKED] ${grant.transport.diagnostic_code || "repo_push_transport_unavailable"}`); output(`NEXT_STEP=${grant.transport.next_step || "读取服务器 operation receipt。"}`); const error = new Error(grant.transport.diagnostic_code || "repo_push_transport_unavailable"); error.receipt = grant.receipt; throw error; } output(`[LL-REPO-PUSH-GRANTED] ${grant.repo} 已获得限时推送许可。现在重试原 git push。`); return grant; } async function requestJson(fetchImpl, url, body, bearerToken = "") { const headers = { "content-type": "application/json" }; if (bearerToken) headers.authorization = `Bearer ${bearerToken}`; const response = await fetchImpl(url, { method: "POST", headers, body: JSON.stringify(body) }); const payload = await readPayload(response); if (!response.ok) throw new Error(payload.error || `request failed (${response.status})`); return payload; } async function readPayload(response) { try { return await response.json(); } catch { return {}; } } function normalizeRepo(value) { const repo = String(value).trim().toLowerCase().replace(/\.git$/, ""); if (!/^bingshuo\/[a-z0-9._-]+$/.test(repo)) throw new Error("--repo must be bingshuo/"); return repo; } function required(value, name) { if (!value) throw new Error(`--${name} is required`); return String(value); } function positiveNumber(value, fallback) { if (value === undefined) return fallback; const number = Number(value); if (!Number.isFinite(number) || number <= 0) throw new Error("--poll must be a positive millisecond value"); return number; } function parseArgs(argv) { const result = {}; for (let index = 0; index < argv.length; index += 2) { const key = String(argv[index] || "").replace(/^--/, ""); if (!key || argv[index + 1] === undefined) throw new Error(`invalid argument: ${argv[index] || ""}`); result[key] = argv[index + 1]; } return result; } async function main() { await authorizeRepoPush(parseArgs(process.argv.slice(2))); } if (require.main === module) { main().catch(error => { process.stderr.write(`[LL-REPO-PUSH-AUTH-FAILED] ${error.message}\n`); process.exit(1); }); } module.exports = { authorizeRepoPush, normalizeRepo, parseArgs };