From bcec7e69346a06d1142b40e1f2a80deaa882a978 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 4 Jun 2026 02:16:17 +0800 Subject: [PATCH] feat(device-pod-cli): v0.2 hwpod prefers HWLAB_API_KEY over legacy key - authHeaders now reads HWLAB_API_KEY (env or --apiKey/--bearerToken) first; only when hwl_live_ prefix is present - when both are set, HWLAB_API_KEY wins and x-hwlab-device-pod-api-key is suppressed - new test asserts the precedence using a fake API key against a local http server - help auth hint updated to mention the preferred credential --- tools/device-pod-cli.test.ts | 41 +++++++++++++++++++++++++++++++++ tools/src/device-pod-cli-lib.ts | 8 +++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/tools/device-pod-cli.test.ts b/tools/device-pod-cli.test.ts index 373bfe5c..12aa352d 100644 --- a/tools/device-pod-cli.test.ts +++ b/tools/device-pod-cli.test.ts @@ -699,3 +699,44 @@ function serverUrl(server: ReturnType) { if (!address || typeof address === "string") throw new Error("server address is unavailable"); return `http://127.0.0.1:${address.port}`; } + +test("device-pod-cli prefers HWLAB_API_KEY over HWLAB_DEVICE_POD_API_KEY when both are set", async () => { + const seen: any[] = []; + const server = createServer((request, response) => { + let chunks: Buffer[] = []; + request.on("data", (chunk) => chunks.push(chunk)); + request.on("end", () => { + const body = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}"); + seen.push({ + url: request.url, + method: request.method, + devicePodKey: request.headers["x-hwlab-device-pod-api-key"] ?? null, + authorization: request.headers["authorization"] ?? null + }); + response.writeHead(200, { "content-type": "application/json" }); + response.end(JSON.stringify({ ok: true, status: "ok", devicePods: [], count: 0 })); + }); + }); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + try { + const port = server.address().port; + const result = await runDevicePodCli(["profile", "list"], { + env: { + HWLAB_RUNTIME_API_URL: `http://127.0.0.1:${port}`, + HWLAB_RUNTIME_NAMESPACE: "hwlab-v02", + HWLAB_RUNTIME_LANE: "v02", + HWLAB_RUNTIME_ENDPOINT_LOCKED: "1", + HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME: "1", + HWLAB_DEVICE_POD_API_KEY: "device-pod-api-key-a", + HWLAB_API_KEY: "hwl_live_user-default-key-1234567890" + }, + now: () => "2026-06-03T00:00:00.000Z" + }); + assert.equal(result.exitCode, 0); + assert.equal(seen.length, 1); + assert.equal(seen[0].devicePodKey, null); + assert.equal(seen[0].authorization, "Bearer hwl_live_user-default-key-1234567890"); + } finally { + await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))); + } +}); diff --git a/tools/src/device-pod-cli-lib.ts b/tools/src/device-pod-cli-lib.ts index d3ab2eb0..08ebb920 100644 --- a/tools/src/device-pod-cli-lib.ts +++ b/tools/src/device-pod-cli-lib.ts @@ -60,7 +60,7 @@ function help() { localProfileAuthority: false, configuration: { apiBaseUrl: "auto-located from assembled HWLAB_RUNTIME_* / HWLAB_DEVICE_POD_* env; manual endpoint arguments are rejected when HWLAB_RUNTIME_ENDPOINT_LOCKED=1", - auth: "assembled HWLAB_DEVICE_POD_API_KEY for AgentRun; browser cookie or explicit user session for local debug" + auth: "preferred HWLAB_API_KEY (hwl_live_...) for AgentRun and CLI; legacy HWLAB_DEVICE_POD_API_KEY or cookie session for transition", }, usage: [ "hwpod profile list", @@ -565,7 +565,10 @@ const uartJsonRpcOptionKeys = [ ]; function defaultOperation(surface: string) { return surface === "workspace" ? "ls" : surface === "debug-probe" ? "status" : "read"; } async function authHeaders(parsed: ParsedArgs, env: EnvLike, endpoint: any) { - const apiKey = text(parsed.devicePodApiKey ?? env.HWLAB_DEVICE_POD_API_KEY); + const userApiKey = text(parsed.apiKey ?? parsed.bearerToken ?? env.HWLAB_API_KEY ?? env.HWLAB_BEARER_TOKEN); + const legacyDevicePodApiKey = text(parsed.devicePodApiKey ?? env.HWLAB_DEVICE_POD_API_KEY); + const useUserApiKey = userApiKey.startsWith("hwl_live_"); + const apiKey = useUserApiKey ? "" : legacyDevicePodApiKey; const explicitCookie = text(parsed.cookie ?? env.HWLAB_SESSION_COOKIE); const sessionToken = text(parsed.sessionToken ?? env.HWLAB_CLOUD_API_SESSION_TOKEN ?? env.HWLAB_SESSION_TOKEN); const bearer = text(parsed.bearerToken ?? env.HWLAB_BEARER_TOKEN); @@ -573,6 +576,7 @@ async function authHeaders(parsed: ParsedArgs, env: EnvLike, endpoint: any) { const cookie = explicitCookie || stateCookie; return clean({ ...(apiKey ? { "x-hwlab-device-pod-api-key": apiKey } : {}), + ...(useUserApiKey ? { authorization: `Bearer ${userApiKey}` } : {}), ...(cookie ? { cookie: cookie.includes("=") ? cookie : `hwlab_session=${encodeURIComponent(cookie)}` } : {}), ...(sessionToken ? { "x-hwlab-session-token": sessionToken } : {}), ...(bearer ? { authorization: `Bearer ${bearer}` } : {})