fix: 优化 device-pod CLI 短入口

This commit is contained in:
Codex
2026-05-31 19:16:13 +08:00
parent c191ada9d0
commit ca714bfa27
9 changed files with 100 additions and 56 deletions
@@ -1,5 +1,5 @@
import { spawnSync } from "node:child_process";
import { existsSync, mkdirSync, rmSync, symlinkSync } from "node:fs";
import { chmodSync, existsSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
import path from "node:path";
const bootRepo = requiredEnv("HWLAB_BOOT_REPO");
@@ -24,6 +24,7 @@ rmSync(checkoutDir, { recursive: true, force: true });
run("git", ["clone", "--no-checkout", "--single-branch", "--branch", bootRef, readUrl, checkoutDir], "/");
run("git", ["checkout", "--detach", bootCommit], checkoutDir);
linkNodeModules();
installDevicePodAlias();
const scriptPath = path.join(checkoutDir, bootSh);
if (!existsSync(scriptPath)) fail(`boot script not found: ${bootSh}`);
@@ -48,6 +49,13 @@ function linkNodeModules(): void {
symlinkSync(runtimeNodeModules, target, "dir");
}
function installDevicePodAlias(): void {
const wrapper = "/usr/local/bin/hwpod";
const cli = path.join(checkoutDir, "tools", "device-pod-cli.mjs");
writeFileSync(wrapper, `#!/usr/bin/env sh\nexec node ${shellArg(cli)} "$@"\n`, { encoding: "utf8" });
chmodSync(wrapper, 0o755);
}
function run(command: string, args: string[], cwd: string, options: { replace?: boolean } = {}): void {
const result = spawnSync(command, args, {
cwd,
@@ -65,3 +73,7 @@ function fail(message: string): never {
process.stderr.write(`${JSON.stringify({ status: "failed", error: "hwlab_env_reuse_launcher_failed", message })}\n`);
process.exit(1);
}
function shellArg(value: string): string {
return `'${value.replace(/'/gu, `'"'"'`)}'`;
}
+4 -1
View File
@@ -259,7 +259,7 @@ manages: many devicePodId
## CLI 实现口径
`tools/device-pod-cli.ts` 是 v0.2 正式 CLI 实现,稳定 runner 入口`/app/skills/device-pod-cli/scripts/device-pod-cli.mjs`。该 `.mjs` 只作为兼容启动器,通过 Bun 运行 TypeScript CLI。正式 CLI 的默认行为是:
`tools/device-pod-cli.ts` 是 v0.2 正式 CLI 实现HWLAB code-agent runner 内的稳定短入口是 `hwpod``/app/skills/device-pod-cli/scripts/device-pod-cli.mjs` 只作为兼容启动器,在 runner PATH 缺少 `hwpod` 时临时 fallback,并应记录为 runner image/package 摩擦点。正式 CLI 的默认行为是:
- `profile list/show` 调用 cloud-api `/v1/device-pods``/status`,只显示服务端脱敏 profile 摘要和 `profileHash`
- `setup first-admin``admin device-pod upsert/grant` 只作为 cloud-api REST wrapper,用于首次空库 seed 或 admin profile/grant 管理;它们接受显式 `--profile-json``--device-pod-json`,不得读取本地 `.device-pod/*.json` 作为权威 profile。
@@ -271,6 +271,7 @@ manages: many devicePodId
- apply-patch 类失败必须返回可定位的 patch hint,例如缺少 `*** End Patch`、hunk 上下文不匹配或 header 错误;调用方应先重新读取目标文件再重试小 hunk,不应默认绕到整文件覆盖。
- mutating job 继续由 cloud-api 侧强制 lease/reasonCLI 只转发 `reason``leaseToken`,不在本地绕过。
- `profile create` 这类本地 profile bootstrap 在正式默认路径中返回 `legacy_profile_create_removed`;管理员应使用 cloud-api admin API 管理服务端 profile/grant。
- DS/device-pod prompt 和 skill 示例必须优先使用 `hwpod`,避免把长路径 wrapper 复制成常态命令;host job 轮询时 job id 必须作为 `status/output/cancel` 的紧随位置参数传入,不能放到 flags 后面或靠 shell 管道解析 JSON。
## D601 F103 v0.2 Gateway SOP
@@ -383,6 +384,8 @@ bun tools/device-pod-cli.ts D601-F103-V2:debug-probe download start \
--timeout-ms 120000
```
Code-agent/DS runner 内执行同类验收时,把 `bun tools/device-pod-cli.ts` 换成 `hwpod`;只有 `hwpod` 不在 PATH 时才临时 fallback 到 `/app/skills/device-pod-cli/scripts/device-pod-cli.mjs`。UART 串口若被 Windows 侧工具占用,应把 COM busy/access denied 记录为可选串口证据缺口,Keil build/download 是否通过以 job output 中的 build summary、`Programming Done``Verify OK``Application running` 为准。
验收通过条件:`client device-pods status` 返回 `status=ok``blocker=null`gateway sessions 中 `gws_D601_F103` 为 online`device-pod-cli` job 进入 `completed`,且 D601 Windows gateway `/status.lastDispatch.operationId` 与该 job 的 `operationId` 一致并有 `lastResultAt`。旧 `gws_d601_win_71_freq` session 只能作为历史 stale 线索,不能作为 v0.2 F103 gateway 的目标命名。
## 测试规格
+1 -1
View File
@@ -204,7 +204,7 @@ export const CODEX_STDIO_BOUNDARY_INSTRUCTIONS = [
"Use the provided workspace and repo-owned Codex stdio session only.",
"Do not read or print secrets, tokens, kubeconfig files, DB URLs, private keys, or raw environment values.",
"For hardware, gateway, box-simu, patch-panel, DAP, PWM, Keil, serial, and Windows skill requests, execute the requested work through the repo-owned Codex stdio session with the available tran wrapper, skill CLI, or project tool that actually reaches the target.",
"For any request that mentions device-pod, device-pod-cli, device-host-cli, a device-pod profile, device-pod-71-freq, device-pod-71-00075-11, D601-F103-V2, or a registered device-pod Keil build/download/UART/debug-probe operation, first use the HWLAB internal skill named device-pod-cli. The canonical skill location is /app/skills/device-pod-cli/SKILL.md; read that manifest and run node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs for the operation. After selecting the target pod or resuming context, run bootsharp --pod-id <devicePodId> before edits, build, or download.",
"For any request that mentions device-pod, device-pod-cli, device-host-cli, a device-pod profile, device-pod-71-freq, device-pod-71-00075-11, D601-F103-V2, or a registered device-pod Keil build/download/UART/debug-probe operation, first use the HWLAB internal skill named device-pod-cli. The canonical skill location is /app/skills/device-pod-cli/SKILL.md; read that manifest and run hwpod for the operation. Use node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs only as a compatibility fallback when hwpod is absent. After selecting the target pod or resuming context, run bootsharp --pod-id <devicePodId> before edits, build, or download.",
"When a matching device-pod profile exists, do not bypass device-pod-cli with direct hwlab-gateway-tran.mjs, Windows Keil skills, serial-monitor skills, keil-cli.py, or ad hoc path discovery. Only drop to tran or Windows-side skills when the device-pod-cli skill explicitly says to bootstrap, install, or repair the lower layer.",
"For registered PC gateway Windows command or skill requests, use the preloaded tran wrapper: node /app/tools/hwlab-gateway-tran.mjs gws_DESKTOP-1MHOD9I:/f/work <cmd|ps|upload|download> [options] -- <args>. The locator before ':' is the gateway session and the path after ':' is the Windows workspace, with /f/work and f:/work both mapping to F:\\work.",
"For Windows cmd, call tran as: node /app/tools/hwlab-gateway-tran.mjs gws_DESKTOP-1MHOD9I:/f/work cmd -- <command>. For PowerShell, call tran as: node /app/tools/hwlab-gateway-tran.mjs gws_DESKTOP-1MHOD9I:/f/work ps -- <script>. The wrapper owns UTF-8, quoting, cwd, EncodedCommand, and stdout/stderr passthrough; do not hand-build nested cmd /c, cd &&, pipes, or quoting workarounds.",
+2 -1
View File
@@ -791,6 +791,7 @@ function dockerfile(baseImage, port, labels = []) {
"COPY tools ./tools",
"COPY skills ./skills",
"COPY deploy ./deploy",
"RUN printf '%s\\n' '#!/usr/bin/env sh' 'exec node /app/tools/device-pod-cli.mjs \"$@\"' > /usr/local/bin/hwpod && chmod 755 /usr/local/bin/hwpod",
`RUN ${runtimeReadinessScript}`,
"RUN cp /app/internal/dev-entrypoint/artifact-runtime.mjs /usr/local/bin/hwlab-dev-artifact-runtime.mjs && chmod 755 /usr/local/bin/hwlab-dev-artifact-runtime.mjs",
"ARG HWLAB_ENVIRONMENT",
@@ -829,7 +830,7 @@ function dockerfile(baseImage, port, labels = []) {
function envReuseDockerfile(baseImage, labels = []) {
const osPackageScript = "set -eu; apt-get update; apt-get install -y --no-install-recommends ca-certificates git; rm -rf /var/lib/apt/lists/*";
const dependencyScript = "set -eu; if [ -f package-lock.json ]; then npm ci --omit=dev --include=optional --ignore-scripts; else npm install --omit=dev --include=optional --ignore-scripts; fi; if command -v bun >/dev/null 2>&1; then bun_path=\"$(command -v bun)\"; if [ \"$bun_path\" != \"/usr/local/bin/bun\" ]; then ln -sf \"$bun_path\" /usr/local/bin/bun; fi; fi; if [ ! -x /usr/local/bin/bun ]; then bun_pkg=\"\"; case \"$(uname -m)\" in x86_64|amd64) bun_pkg=\"@oven/bun-linux-x64@1.3.13\" ;; aarch64|arm64) bun_pkg=\"@oven/bun-linux-aarch64@1.3.13\" ;; *) echo \"unsupported bun architecture: $(uname -m)\" >&2; false ;; esac; npm install --no-save --omit=dev --include=optional --ignore-scripts \"$bun_pkg\"; bun_bin=\"node_modules/${bun_pkg%@*}/bin/bun\"; test -x \"$bun_bin\"; ln -sf \"/opt/hwlab-env/$bun_bin\" /usr/local/bin/bun; fi";
const readinessScript = "set -eu; command -v node >/dev/null; command -v npm >/dev/null; command -v git >/dev/null; command -v sh >/dev/null; test -d /opt/hwlab-env/node_modules; test -x /usr/local/bin/bun; /usr/local/bin/bun --version >/tmp/hwlab-env-bun-version.txt";
const readinessScript = "set -eu; command -v node >/dev/null; command -v npm >/dev/null; command -v git >/dev/null; command -v sh >/dev/null; test -d /opt/hwlab-env/node_modules; test -x /usr/local/bin/bun; /usr/local/bin/bun --version >/tmp/hwlab-env-bun-version.txt; printf '%s\\n' '#!/usr/bin/env sh' 'exec node /workspace/hwlab-boot/repo/tools/device-pod-cli.mjs \"$@\"' > /usr/local/bin/hwpod; chmod 755 /usr/local/bin/hwpod";
return [
`FROM ${baseImage}`,
"WORKDIR /opt/hwlab-env",
+1
View File
@@ -200,6 +200,7 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots
const artifactPublishSource = await readFile("scripts/artifact-publish.mjs", "utf8");
assert.match(artifactPublishSource, /\["run", "--cwd", "web\/hwlab-cloud-web", "check"\]/u);
assert.match(artifactPublishSource, /cloud web source check failed before BuildKit image build/u);
assert.match(artifactPublishSource, /\/usr\/local\/bin\/hwpod/u);
assert.doesNotMatch(artifactPublishSource, /Run bun run --cwd web\/hwlab-cloud-web build, fix the static asset build/u);
const workloads = await readFile(path.join(outDir, "runtime-v02", "workloads.yaml"), "utf8");
+33 -33
View File
@@ -16,8 +16,8 @@ Canonical skill location: `/app/skills/device-pod-cli/SKILL.md`. Do not create d
## Runtime Contract
- The stable runner entrypoint is `node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs`.
- The canonical implementation is `/app/tools/device-pod-cli.ts`; `/app/tools/device-pod-cli.mjs` and the skill script are compatibility wrappers that launch the Bun+TypeScript CLI.
- The stable runner entrypoint is `hwpod`. Use `node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs` only as a compatibility fallback when `hwpod` is missing from PATH, and report that as a runner image/package friction point.
- The canonical implementation is `/app/tools/device-pod-cli.ts`; `hwpod`, `/app/tools/device-pod-cli.mjs`, and the skill script are wrappers that launch the Bun+TypeScript CLI.
- All commands emit JSON. A command with no output is a failure.
- Formal v0.2 mode is cloud-api authority: the CLI calls `/v1/device-pods...` REST APIs and must not read `.device-pod/*.json` as the source of truth for gateway session, workspace root, probe UID, UART port, or host CLI.
- Server-side profile, grant, lease, job authority, and profile hash are owned by `hwlab-cloud-api`; `hwlab-device-pod` and `device-host-cli` execute only after cloud-api authorization.
@@ -48,7 +48,7 @@ HWLAB_SESSION_COOKIE='hwlab_session=<token>'
Use `login` only when a username/password credential is explicitly available:
```sh
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs login --api-base-url <url> --username <user> --password <password>
hwpod login --api-base-url <url> --username <user> --password <password>
```
The `setCookie` value in the JSON output can be passed back as `--cookie` or `HWLAB_SESSION_COOKIE`.
@@ -58,21 +58,21 @@ The `setCookie` value in the JSON output can be passed back as `--cookie` or `HW
List visible Device Pods through cloud-api authority:
```sh
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs profile list --api-base-url <url> --session-token <token>
hwpod profile list --api-base-url <url> --session-token <token>
```
Show one visible Device Pod status and redacted server profile summary:
```sh
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs profile show --pod-id device-pod-71-freq --api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs health --pod-id device-pod-71-freq --api-base-url <url> --session-token <token>
hwpod profile show --pod-id device-pod-71-freq --api-base-url <url> --session-token <token>
hwpod health --pod-id device-pod-71-freq --api-base-url <url> --session-token <token>
```
Start every Device Pod task or resumed context with `bootsharp` so the agent sees the current workspace tree and `AGENTS.md` through the formal REST path:
```sh
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs bootsharp --pod-id <devicePodId> --api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs <devicePodId>:workspace:/ bootsharp --api-base-url <url> --session-token <token>
hwpod bootsharp --pod-id <devicePodId> --api-base-url <url> --session-token <token>
hwpod <devicePodId>:workspace:/ bootsharp --api-base-url <url> --session-token <token>
```
For the D601 F103 v2 board, the registered `devicePodId` is `D601-F103-V2`; its server-side profile points to `F:\Work\D601-HWLAB`, Keil project `projects/01_baseline/Projects/MDK-ARM/atk_f103.uvprojx`, target `USART`, `C:\Keil_v5\UV4\UV4.exe`, and UART `COM9` at `115200`.
@@ -80,70 +80,70 @@ For the D601 F103 v2 board, the registered `devicePodId` is `D601-F103-V2`; its
Diagnose auth, grant, lease, and gateway blockers without touching local profiles:
```sh
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs doctor --pod-id device-pod-71-freq --api-base-url <url> --session-token <token>
hwpod doctor --pod-id device-pod-71-freq --api-base-url <url> --session-token <token>
```
Acquire and inspect a lease for mutating jobs:
```sh
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs lease acquire --pod-id device-pod-71-freq --reason "build smoke" --api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs lease current --pod-id device-pod-71-freq --api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs lease release --pod-id device-pod-71-freq --lease-token <lease-token> --api-base-url <url> --session-token <token>
hwpod lease acquire --pod-id device-pod-71-freq --reason "build smoke" --api-base-url <url> --session-token <token>
hwpod lease current --pod-id device-pod-71-freq --api-base-url <url> --session-token <token>
hwpod lease release --pod-id device-pod-71-freq --lease-token <lease-token> --api-base-url <url> --session-token <token>
```
Run read-only workspace, debug, and I/O jobs:
```sh
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:workspace:/ ls --api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:workspace:/src rg main --api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:debug-probe chip-id --api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:io-probe:/uart/1 read --duration-ms 5000 --api-base-url <url> --session-token <token>
hwpod device-pod-71-freq:workspace:/ ls --api-base-url <url> --session-token <token>
hwpod device-pod-71-freq:workspace:/src rg main --api-base-url <url> --session-token <token>
hwpod device-pod-71-freq:debug-probe chip-id --api-base-url <url> --session-token <token>
hwpod device-pod-71-freq:io-probe:/uart/1 read --duration-ms 5000 --api-base-url <url> --session-token <token>
```
Run mutating jobs only with reason and lease token when required by cloud-api:
```sh
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \
hwpod \
device-pod-71-freq:workspace:/ apply-patch \
--reason "DEV edit" --lease-token <lease-token> \
--api-base-url <url> --session-token <token> < patch.diff
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \
hwpod \
device-pod-71-freq:workspace:/ put User/new.c \
--reason "DEV edit" --lease-token <lease-token> \
--api-base-url <url> --session-token <token> < file
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \
hwpod \
device-pod-71-freq:workspace:/ rm User/old.c --missing-ok \
--reason "remove obsolete source" --lease-token <lease-token> \
--api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \
hwpod \
device-pod-71-freq:workspace:/ rmdir User/empty \
--reason "remove empty folder" --lease-token <lease-token> \
--api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \
hwpod \
device-pod-71-freq:workspace:/ keil add-source User/new.c --group User \
--reason "sync Keil project" --lease-token <lease-token> \
--api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \
hwpod \
device-pod-71-freq:workspace:/ keil remove-source User/old.c \
--reason "sync Keil project" --lease-token <lease-token> \
--api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \
hwpod \
device-pod-71-freq:workspace:/ build start \
--reason "build smoke" --lease-token <lease-token> \
--api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \
hwpod \
device-pod-71-freq:debug-probe download start \
--reason "DEV smoke" --lease-token <lease-token> \
--api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \
hwpod \
device-pod-71-freq:debug-probe reset \
--reason "reset smoke" --lease-token <lease-token> \
--api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \
hwpod \
device-pod-71-freq:io-probe:/uart/1 write "AT" \
--reason "UART write smoke" --lease-token <lease-token> \
--api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \
hwpod \
device-pod-71-freq:io-probe:/uart/1 jsonrpc gpio.read \
--params-json '{"pin":"PB5"}' --expect-result-field value \
--reason "UART JSON-RPC smoke" --lease-token <lease-token> \
@@ -153,16 +153,16 @@ node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \
Inspect or cancel jobs through cloud-api:
```sh
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs job status --pod-id device-pod-71-freq <jobId> --api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs job output --pod-id device-pod-71-freq <jobId> --api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs job cancel --pod-id device-pod-71-freq <jobId> --api-base-url <url> --session-token <token>
hwpod job status --pod-id device-pod-71-freq <jobId> --api-base-url <url> --session-token <token>
hwpod job output --pod-id device-pod-71-freq <jobId> --api-base-url <url> --session-token <token>
hwpod job cancel --pod-id device-pod-71-freq <jobId> --api-base-url <url> --session-token <token>
```
Selector aliases also support job status/output/cancel after build/download:
```sh
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:workspace:/ build status <jobId> --api-base-url <url> --session-token <token>
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:debug-probe download output <jobId> --api-base-url <url> --session-token <token>
hwpod device-pod-71-freq:workspace:/ build status <jobId> --api-base-url <url> --session-token <token>
hwpod device-pod-71-freq:debug-probe download output <jobId> --api-base-url <url> --session-token <token>
```
## Removed Legacy Path
@@ -173,7 +173,7 @@ Do not treat local files such as `.device-pod/device-pod-71-freq.json` or `DEVIC
## Operation Rules
- Use `device-pod-cli` first for workspace, build, download, reset, UART, and debug-probe tasks.
- Use `hwpod`/`device-pod-cli` first for workspace, build, download, reset, UART, and debug-probe tasks.
- After selecting a pod or resuming context, run `bootsharp --pod-id <devicePodId>` before edits, build, or download.
- Prefer `workspace apply-patch` for source edits. Use `workspace put` only for intentional whole-file writes or new files, because it bypasses hunk-level conflict detection.
- If apply-patch fails, read the returned `patchHint`, re-read the current file with `workspace cat` or `workspace rg`, and retry a smaller exact-context hunk before using whole-file write.
+27
View File
@@ -418,10 +418,37 @@ test("hwlab-cli client harness submits waits and audits trace friction", async (
});
assert.equal(audit.exitCode, 0);
assert.equal(audit.payload.status, "friction_detected");
assert.ok(audit.payload.frictionSignals.some((signal) => signal.kind === "long_device_pod_cli_path"));
assert.ok(audit.payload.frictionSignals.some((signal) => signal.kind === "workspace_put_text_edit"));
assert.ok(audit.payload.frictionSignals.some((signal) => signal.kind === "missing_bootsharp"));
});
test("hwlab-cli client harness audit treats hwpod bootsharp flow as clean", async () => {
const audit = await runHwlabCli([
"client",
"harness",
"audit",
"trc_hwpod",
"--require-bootsharp",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a"
], {
fetchImpl: async () => new Response(JSON.stringify({
traceId: "trc_hwpod",
events: [
{ type: "tool_call", command: "hwpod bootsharp --pod-id D601-F103-V2 --api-base-url http://api.test --session-token session-a" },
{ type: "tool_call", command: "hwpod D601-F103-V2:workspace:/ build status job_123 --api-base-url http://api.test --session-token session-a" }
]
}), { status: 200 })
});
assert.equal(audit.exitCode, 0);
assert.equal(audit.payload.status, "clean");
assert.equal(audit.payload.summary.hasHwpod, true);
assert.equal(audit.payload.summary.hasBootsharp, true);
});
test("hwlab-cli client workbench summary probes Cloud Web non-visual surfaces", async () => {
const seen: string[] = [];
const result = await runHwlabCli(["client", "workbench", "summary", "--base-url", "http://web.test", "--cookie", "session-a", "--pod-id", "device-pod-71-freq"], {
+17 -17
View File
@@ -55,22 +55,22 @@ function help() {
auth: "--session-token, --cookie, --bearer-token, or matching HWLAB_* env"
},
usage: [
"device-pod-cli login --api-base-url URL --username USER --password PASS",
"device-pod-cli setup first-admin --api-base-url URL --username USER --password PASS --pod-id device-pod-71-freq --profile-json JSON",
"device-pod-cli admin device-pod upsert --api-base-url URL --session-token TOKEN --pod-id device-pod-71-freq --profile-json JSON",
"device-pod-cli admin grant --api-base-url URL --session-token TOKEN --pod-id device-pod-71-freq --user-id USER_ID",
"device-pod-cli profile list --api-base-url URL --session-token TOKEN",
"device-pod-cli profile show --pod-id device-pod-71-freq --api-base-url URL --session-token TOKEN",
"device-pod-cli bootsharp --pod-id D601-F103-V2 --api-base-url URL --session-token TOKEN",
"device-pod-cli D601-F103-V2:workspace:/ bootsharp --api-base-url URL --session-token TOKEN",
"device-pod-cli device-pod-71-freq:workspace:/ ls --api-base-url URL --session-token TOKEN",
"device-pod-cli device-pod-71-freq:workspace:/ put User/new.c --reason TEXT --lease-token TOKEN < file",
"device-pod-cli device-pod-71-freq:workspace:/ rmdir User/empty --reason TEXT --lease-token TOKEN",
"device-pod-cli device-pod-71-freq:workspace:/ keil add-source User/new.c --group User --reason TEXT --lease-token TOKEN",
"device-pod-cli device-pod-71-freq:workspace:/ build start --reason TEXT --lease-token TOKEN",
"device-pod-cli device-pod-71-freq:io-probe:/uart/1 jsonrpc gpio.read --params-json '{\"pin\":\"PB5\'} --reason TEXT --lease-token TOKEN",
"device-pod-cli job output --pod-id device-pod-71-freq <jobId> --api-base-url URL --session-token TOKEN",
"device-pod-cli lease acquire --pod-id device-pod-71-freq --reason TEXT --api-base-url URL --session-token TOKEN"
"hwpod login --api-base-url URL --username USER --password PASS",
"hwpod setup first-admin --api-base-url URL --username USER --password PASS --pod-id device-pod-71-freq --profile-json JSON",
"hwpod admin device-pod upsert --api-base-url URL --session-token TOKEN --pod-id device-pod-71-freq --profile-json JSON",
"hwpod admin grant --api-base-url URL --session-token TOKEN --pod-id device-pod-71-freq --user-id USER_ID",
"hwpod profile list --api-base-url URL --session-token TOKEN",
"hwpod profile show --pod-id device-pod-71-freq --api-base-url URL --session-token TOKEN",
"hwpod bootsharp --pod-id D601-F103-V2 --api-base-url URL --session-token TOKEN",
"hwpod D601-F103-V2:workspace:/ bootsharp --api-base-url URL --session-token TOKEN",
"hwpod device-pod-71-freq:workspace:/ ls --api-base-url URL --session-token TOKEN",
"hwpod device-pod-71-freq:workspace:/ put User/new.c --reason TEXT --lease-token TOKEN < file",
"hwpod device-pod-71-freq:workspace:/ rmdir User/empty --reason TEXT --lease-token TOKEN",
"hwpod device-pod-71-freq:workspace:/ keil add-source User/new.c --group User --reason TEXT --lease-token TOKEN",
"hwpod device-pod-71-freq:workspace:/ build start --reason TEXT --lease-token TOKEN",
"hwpod device-pod-71-freq:io-probe:/uart/1 jsonrpc gpio.read --params-json '{\"pin\":\"PB5\'} --reason TEXT --lease-token TOKEN",
"hwpod job output --pod-id device-pod-71-freq <jobId> --api-base-url URL --session-token TOKEN",
"hwpod lease acquire --pod-id device-pod-71-freq --reason TEXT --api-base-url URL --session-token TOKEN"
],
startupProbe: "Run bootsharp first after selecting a Device Pod or resuming context; it returns workspace tree and AGENTS.md hints through the formal REST job path."
});
@@ -449,7 +449,7 @@ const uartJsonRpcOptionKeys = [
"baudRate"
];
function defaultOperation(surface: string) { return surface === "workspace" ? "ls" : surface === "debug-probe" ? "status" : "read"; }
function apiBaseUrl(parsed: ParsedArgs, env: EnvLike) { const value = text(parsed.apiBaseUrl ?? parsed.apiUrl ?? env.HWLAB_DEVICE_POD_API_URL ?? env.HWLAB_CLOUD_API_URL); if (!value) throw cliError("api_base_url_required", "device-pod-cli requires --api-base-url or HWLAB_DEVICE_POD_API_URL/HWLAB_CLOUD_API_URL"); return value.replace(/\/+$/u, ""); }
function apiBaseUrl(parsed: ParsedArgs, env: EnvLike) { const value = text(parsed.apiBaseUrl ?? parsed.apiUrl ?? env.HWLAB_DEVICE_POD_API_URL ?? env.HWLAB_CLOUD_API_URL); if (!value) throw cliError("api_base_url_required", "hwpod requires --api-base-url or HWLAB_DEVICE_POD_API_URL/HWLAB_CLOUD_API_URL"); return value.replace(/\/+$/u, ""); }
function authHeaders(parsed: ParsedArgs, env: EnvLike) { const cookie = text(parsed.cookie ?? env.HWLAB_SESSION_COOKIE); const sessionToken = text(parsed.sessionToken ?? env.HWLAB_DEVICE_POD_SESSION_TOKEN ?? env.HWLAB_CLOUD_API_SESSION_TOKEN ?? env.HWLAB_SESSION_TOKEN); const bearer = text(parsed.bearerToken ?? env.HWLAB_BEARER_TOKEN); return clean({ ...(cookie ? { cookie: cookie.includes("=") ? cookie : `hwlab_session=${encodeURIComponent(cookie)}` } : {}), ...(sessionToken ? { "x-hwlab-session-token": sessionToken } : {}), ...(bearer ? { authorization: `Bearer ${bearer}` } : {}) }); }
function responsePayload(action: string, response: any, extra: Record<string, unknown> = {}) { const success = response.status >= 200 && response.status < 300 && response.body?.ok !== false; return { ok: success, action, status: success ? "succeeded" : "failed", httpStatus: response.status, ...extra, body: response.body }; }
function withMeta(payload: any, now: () => string) { return { generatedAt: now(), cli: CLI_NAME, version: VERSION, ...payload }; }
+2 -2
View File
@@ -1165,7 +1165,7 @@ function auditTrace(traceObject: any, { requireBootsharp = false } = {}) {
"long_device_pod_cli_path",
/node\s+\/app\/(?:tools\/device-pod-cli\.mjs|skills\/device-pod-cli\/scripts\/device-pod-cli\.mjs)/u,
textValue,
"Prefer the main hwlab-cli client entry or short documented device-pod command."
"Prefer hwpod in HWLAB code-agent runners; the long wrapper path is only a compatibility fallback when hwpod is absent."
),
signal(
"temporary_script_workaround",
@@ -1204,7 +1204,7 @@ function auditTrace(traceObject: any, { requireBootsharp = false } = {}) {
},
frictionSignals: signals,
recommendation: signals.length > 0
? "Treat these as tool-friction signals and improve named CLI/device-pod operations; this is not a runtime gate."
? "Treat these as tool-friction signals and improve named CLI/device-pod operations; this is not a runtime gate. Prefer hwpod for device-pod commands."
: "No obvious device-pod transport friction in retained trace events."
};
}