@whobot/cli 2.0.2 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/install.mjs +41 -2
  2. package/package.json +1 -1
package/bin/install.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { createHash } from "node:crypto";
2
+ import { createHash, randomUUID } from "node:crypto";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import {
5
5
  accessSync,
@@ -678,9 +678,14 @@ export async function runExplicitInstall(argv, env) {
678
678
  const staging = await fs.mkdtemp(path.join(os.tmpdir(), "whobot-install-preflight-"));
679
679
  const stagedNative = path.join(staging, path.basename(packageNativeBinary(prefix)));
680
680
  const updateArgs = ["update", "--yes", "--target-version", pkg.version];
681
+ const installId = randomUUID();
682
+ const reportAttrs = { installId, version: pkg.version, channel: bakedChannel(), previousVersion: registered };
683
+ let staged = false;
681
684
  let prepared;
682
685
  try {
683
686
  await ensureNativeBinary(env, { force: true, dest: stagedNative, version: pkg.version });
687
+ staged = true;
688
+ reportViaNative(stagedNative, "install_started", "ok", { attrs: reportAttrs }, env);
684
689
  verifyNpmEntryVersion(stagedNative, pkg.version, env);
685
690
  if (resetLocal) runResetLocal(stagedNative, env);
686
691
  runInstallPhase(stagedNative, ["update", "check-install", "--previous-binary", packageNativeBinary(prefix)], env, "binding", registered);
@@ -695,17 +700,43 @@ export async function runExplicitInstall(argv, env) {
695
700
  const installed = await verifyInstalledCLI(tools, npmEnv, prefix, pkg.version, env, ignoreLauncher);
696
701
  runInstallPhase(installed.native, updateArgs, env, "verify", registered);
697
702
  await prepared.discard();
703
+ reportViaNative(installed.native, "install_succeeded", "ok", { attrs: reportAttrs }, env);
698
704
  if (resetLocal) logStderr("本机登录已清理,请重新登录。");
699
705
  logStderr(`CLI 软件安装已完成;请更新 Skill 至对应渠道最新版,重载宿主或新建任务后重新预检。v2 后续更新请运行 ${COMMAND} update,并执行确认卡中的命令。`);
700
706
  return entry;
701
707
  } catch (err) {
702
708
  if (prepared) await prepared.restore();
709
+ // Before the program is staged there is nothing to report with.
710
+ if (staged) {
711
+ reportViaNative(stagedNative, "install_failed", "failed", { errorCode: failureCode(err) || "INSTALL_FAILED", attrs: reportAttrs }, env);
712
+ }
703
713
  throw err;
704
714
  } finally {
705
715
  await fs.rm(staging, { recursive: true, force: true });
706
716
  }
707
717
  }
708
718
 
719
+ // reportViaNative records an install/update fact for analytics (ADR-0020)
720
+ // through the native program's hidden reporter, which carries its compiled API
721
+ // address, client id and AI host. Its outcome is ignored and it is bounded, so
722
+ // it never fails or stalls an install or update.
723
+ const REPORT_TIMEOUT_MS = 5000;
724
+ export function reportViaNative(native, name, result, { errorCode = "", attrs = {} } = {}, env = process.env) {
725
+ const args = ["telemetry", "report", "--name", name, "--result", result];
726
+ if (errorCode) args.push("--error-code", errorCode);
727
+ for (const [key, value] of Object.entries(attrs)) {
728
+ if (value !== undefined && value !== null && value !== "") args.push("--attr", `${key}=${value}`);
729
+ }
730
+ const reportEnv = { ...env };
731
+ delete reportEnv.WHOBOT_UPDATE_PHASE;
732
+ spawnSync(native, args, { env: reportEnv, stdio: "ignore", timeout: REPORT_TIMEOUT_MS, windowsHide: true });
733
+ }
734
+
735
+ function failureCode(err) {
736
+ const code = err && typeof err.code === "string" ? err.code : "";
737
+ return /^[A-Z][A-Z0-9_]{0,63}$/.test(code) ? code : "";
738
+ }
739
+
709
740
  function runInstallPhase(native, args, env, phase, previous, wantVersion = pkg.version) {
710
741
  const result = spawnSync(native, args, {
711
742
  encoding: "utf8",
@@ -856,10 +887,12 @@ export async function runWhobot(argv, env) {
856
887
  }
857
888
  await recoverNativeBackup(nativeBinaryPath(pkgRoot));
858
889
  if (isUpdateApply(argv)) {
890
+ const updateAttrs = { updateId: randomUUID(), fromVersion: pkg.version };
891
+ let started = false;
859
892
  try {
860
893
  rejectExternalChannel(env);
861
894
  parseChannelAndForward(argv);
862
- confirmedTargetVersion(argv);
895
+ updateAttrs.toVersion = confirmedTargetVersion(argv);
863
896
  let currentNative = nativeBinaryPath(pkgRoot);
864
897
  let preflightDir;
865
898
  let pre;
@@ -902,7 +935,10 @@ export async function runWhobot(argv, env) {
902
935
  if (!preflight.ok || preflight.data?.installedVersion !== pkg.version || preflight.next?.action !== "done") {
903
936
  throw new InstallerError("更新前检查没有确认可以替换程序", "UPDATE_VERIFY_FAILED");
904
937
  }
938
+ reportViaNative(nativeBinaryPath(pkgRoot), "update_started", "ok", { attrs: updateAttrs }, env);
939
+ started = true;
905
940
  const updated = await runExplicitUpdate(argv, env);
941
+ reportViaNative(updated.native, "update_succeeded", "replaced", { attrs: { ...updateAttrs, fromVersion: updated.previous, toVersion: updated.installedVersion } }, env);
906
942
  spawnNative(
907
943
  updated.native,
908
944
  updated.forwarded,
@@ -916,6 +952,9 @@ export async function runWhobot(argv, env) {
916
952
  );
917
953
  } catch (err) {
918
954
  const code = err?.code === "CHANNEL_INVALID" ? "CHANNEL_INVALID" : "UPDATE_VERIFY_FAILED";
955
+ if (started) {
956
+ reportViaNative(nativeBinaryPath(pkgRoot), "update_failed", "failed", { errorCode: failureCode(err) || code, attrs: updateAttrs }, env);
957
+ }
919
958
  const message = err && err.message ? err.message : String(err);
920
959
  writeJSONFailure(code, message);
921
960
  process.stderr.write(`${message}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whobot/cli",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "Whobot CLI: npm-managed JS launcher that runs the in-package Go binary.",
5
5
  "type": "module",
6
6
  "bin": {