@birdybeep/cli 0.1.0 → 0.2.0

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.
package/dist/bin.cjs CHANGED
@@ -169,8 +169,9 @@ async function dispatch(argv, deps) {
169
169
  io.errline(`birdybeep ${path}: unknown option "${unknown}".`);
170
170
  return EXIT.USAGE;
171
171
  }
172
+ let code;
172
173
  try {
173
- return await command.run({ args, flags, io });
174
+ code = await command.run({ args, flags, io });
174
175
  } catch (err) {
175
176
  if (err instanceof MissingInputError) {
176
177
  io.errline(
@@ -181,6 +182,13 @@ async function dispatch(argv, deps) {
181
182
  io.errline(`birdybeep ${path}: ${err instanceof Error ? err.message : String(err)}`);
182
183
  return EXIT.ERROR;
183
184
  }
185
+ if (deps.notifyUpdate !== void 0) {
186
+ try {
187
+ await deps.notifyUpdate({ command: pathParts[0] ?? "", flags, io });
188
+ } catch {
189
+ }
190
+ }
191
+ return code;
184
192
  }
185
193
 
186
194
  // src/commands/agent.ts
@@ -336,6 +344,12 @@ function resolveApiUrl() {
336
344
  if (env !== void 0 && env.length > 0) return env;
337
345
  return readCliConfig().apiUrl ?? DEFAULT_API_URL;
338
346
  }
347
+ var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
348
+ function resolveRegistryUrl() {
349
+ const env = process.env["npm_config_registry"];
350
+ if (env !== void 0 && env.length > 0) return env;
351
+ return DEFAULT_REGISTRY_URL;
352
+ }
339
353
 
340
354
  // src/diagnostics.ts
341
355
  var import_agent_core3 = require("@birdybeep/agent-core");
@@ -626,7 +640,7 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint)
626
640
  }
627
641
 
628
642
  // src/version.ts
629
- var CLI_VERSION = "0.1.0".length > 0 ? "0.1.0" : "0.0.0";
643
+ var CLI_VERSION = "0.2.0".length > 0 ? "0.2.0" : "0.0.0";
630
644
 
631
645
  // src/commands/pair.ts
632
646
  var DEFAULT_POLL_INTERVAL_MS = 2e3;
@@ -963,13 +977,152 @@ function buildCommands() {
963
977
  ];
964
978
  }
965
979
 
980
+ // src/update-check.ts
981
+ var import_node_fs3 = require("fs");
982
+ var import_node_path2 = require("path");
983
+ var import_agent_core13 = require("@birdybeep/agent-core");
984
+ var PACKAGE_NAME = "@birdybeep/cli";
985
+ var PACKAGE_PATH = "@birdybeep%2Fcli";
986
+ var UPDATE_CACHE_FILE = "update-check.json";
987
+ var DEFAULT_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
988
+ var DEFAULT_TIMEOUT_MS = 1500;
989
+ var SKIP_COMMANDS = /* @__PURE__ */ new Set(["hook", "report-status"]);
990
+ var SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
991
+ function parseSemver(input) {
992
+ const m = SEMVER_RE.exec(input.trim());
993
+ if (m === null) return null;
994
+ return {
995
+ major: Number(m[1]),
996
+ minor: Number(m[2]),
997
+ patch: Number(m[3]),
998
+ prerelease: m[4] !== void 0 ? m[4].split(".") : []
999
+ };
1000
+ }
1001
+ function comparePrerelease(a, b) {
1002
+ if (a.length === 0 && b.length === 0) return 0;
1003
+ if (a.length === 0) return 1;
1004
+ if (b.length === 0) return -1;
1005
+ const len = Math.min(a.length, b.length);
1006
+ for (let i = 0; i < len; i++) {
1007
+ const ai = a[i];
1008
+ const bi = b[i];
1009
+ const aNum = /^\d+$/.test(ai);
1010
+ const bNum = /^\d+$/.test(bi);
1011
+ if (aNum && bNum) {
1012
+ const d = Number(ai) - Number(bi);
1013
+ if (d !== 0) return d < 0 ? -1 : 1;
1014
+ } else if (aNum) {
1015
+ return -1;
1016
+ } else if (bNum) {
1017
+ return 1;
1018
+ } else if (ai !== bi) {
1019
+ return ai < bi ? -1 : 1;
1020
+ }
1021
+ }
1022
+ if (a.length === b.length) return 0;
1023
+ return a.length < b.length ? -1 : 1;
1024
+ }
1025
+ function compareSemver(a, b) {
1026
+ if (a.major !== b.major) return a.major < b.major ? -1 : 1;
1027
+ if (a.minor !== b.minor) return a.minor < b.minor ? -1 : 1;
1028
+ if (a.patch !== b.patch) return a.patch < b.patch ? -1 : 1;
1029
+ return comparePrerelease(a.prerelease, b.prerelease);
1030
+ }
1031
+ function isNewer(current, latest) {
1032
+ const cur = parseSemver(current);
1033
+ const lat = parseSemver(latest);
1034
+ return cur !== null && lat !== null && compareSemver(cur, lat) < 0;
1035
+ }
1036
+ function updateCachePath() {
1037
+ return (0, import_node_path2.join)((0, import_agent_core13.birdyBeepConfigDir)(), UPDATE_CACHE_FILE);
1038
+ }
1039
+ function readUpdateCache() {
1040
+ try {
1041
+ const parsed = JSON.parse((0, import_node_fs3.readFileSync)(updateCachePath(), "utf8"));
1042
+ if (typeof parsed !== "object" || parsed === null) return null;
1043
+ const { checkedAt, latest } = parsed;
1044
+ if (typeof checkedAt !== "number") return null;
1045
+ if (latest !== null && typeof latest !== "string") return null;
1046
+ return { checkedAt, latest };
1047
+ } catch {
1048
+ return null;
1049
+ }
1050
+ }
1051
+ function writeUpdateCache(cache) {
1052
+ (0, import_node_fs3.mkdirSync)((0, import_agent_core13.birdyBeepConfigDir)(), { recursive: true, mode: 448 });
1053
+ (0, import_node_fs3.writeFileSync)(updateCachePath(), `${JSON.stringify(cache)}
1054
+ `, { mode: 384 });
1055
+ }
1056
+ async function fetchLatestVersion(registryUrl, fetchImpl, timeoutMs) {
1057
+ const url = `${registryUrl.replace(/\/+$/, "")}/${PACKAGE_PATH}/latest`;
1058
+ const controller = new AbortController();
1059
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
1060
+ if (typeof timer.unref === "function") timer.unref();
1061
+ try {
1062
+ const res = await fetchImpl(url, {
1063
+ headers: { accept: "application/json" },
1064
+ signal: controller.signal
1065
+ });
1066
+ if (!res.ok) throw new Error(`registry responded ${res.status}`);
1067
+ const body = await res.json();
1068
+ if (typeof body.version !== "string" || body.version.length === 0) {
1069
+ throw new Error("registry response had no version");
1070
+ }
1071
+ return body.version;
1072
+ } finally {
1073
+ clearTimeout(timer);
1074
+ }
1075
+ }
1076
+ function renderNotice(current, latest) {
1077
+ return `a new version of birdybeep is available: ${current} \u2192 ${latest}
1078
+ upgrade with: npm install -g ${PACKAGE_NAME}@latest`;
1079
+ }
1080
+ async function maybeNotifyUpdate(opts) {
1081
+ try {
1082
+ if (opts.command !== void 0 && SKIP_COMMANDS.has(opts.command)) return;
1083
+ if (opts.flags.json || opts.flags.nonInteractive) return;
1084
+ const env = opts.env ?? process.env;
1085
+ if (env["BIRDYBEEP_NO_UPDATE_NOTIFIER"] || env["NO_UPDATE_NOTIFIER"] || env["CI"]) return;
1086
+ const isTTY = opts.isTTY ?? Boolean(process.stderr.isTTY);
1087
+ if (!isTTY) return;
1088
+ const current = opts.currentVersion ?? CLI_VERSION;
1089
+ const now = opts.now ?? Date.now();
1090
+ const intervalMs = opts.intervalMs ?? DEFAULT_CHECK_INTERVAL_MS;
1091
+ const readCache = opts.readCache ?? readUpdateCache;
1092
+ const writeCache = opts.writeCache ?? writeUpdateCache;
1093
+ let cache = readCache();
1094
+ if (cache === null || now - cache.checkedAt >= intervalMs) {
1095
+ let latest = cache?.latest ?? null;
1096
+ try {
1097
+ latest = await fetchLatestVersion(
1098
+ opts.registryUrl ?? resolveRegistryUrl(),
1099
+ opts.fetchImpl ?? fetch,
1100
+ opts.timeoutMs ?? DEFAULT_TIMEOUT_MS
1101
+ );
1102
+ } catch {
1103
+ }
1104
+ cache = { checkedAt: now, latest };
1105
+ try {
1106
+ writeCache(cache);
1107
+ } catch {
1108
+ }
1109
+ }
1110
+ if (cache.latest !== null && isNewer(current, cache.latest)) {
1111
+ opts.io.errline(renderNotice(current, cache.latest));
1112
+ }
1113
+ } catch {
1114
+ }
1115
+ }
1116
+
966
1117
  // src/cli.ts
967
1118
  function runCli(argv, deps = {}) {
1119
+ const notifyUpdate = deps.updateCheck === false ? void 0 : (ctx) => maybeNotifyUpdate({ ...ctx, ...deps.updateCheck ?? {} });
968
1120
  return dispatch(argv, {
969
1121
  version: CLI_VERSION,
970
1122
  commands: deps.commands ?? buildCommands(),
971
1123
  stdout: deps.stdout ?? process.stdout,
972
1124
  stderr: deps.stderr ?? process.stderr,
1125
+ ...notifyUpdate !== void 0 ? { notifyUpdate } : {},
973
1126
  ...deps.ensureConfig !== void 0 ? { ensureConfig: deps.ensureConfig } : {}
974
1127
  });
975
1128
  }