@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/index.cjs CHANGED
@@ -205,8 +205,9 @@ async function dispatch(argv, deps) {
205
205
  io.errline(`birdybeep ${path}: unknown option "${unknown}".`);
206
206
  return EXIT.USAGE;
207
207
  }
208
+ let code;
208
209
  try {
209
- return await command.run({ args, flags, io });
210
+ code = await command.run({ args, flags, io });
210
211
  } catch (err) {
211
212
  if (err instanceof MissingInputError) {
212
213
  io.errline(
@@ -217,6 +218,13 @@ async function dispatch(argv, deps) {
217
218
  io.errline(`birdybeep ${path}: ${err instanceof Error ? err.message : String(err)}`);
218
219
  return EXIT.ERROR;
219
220
  }
221
+ if (deps.notifyUpdate !== void 0) {
222
+ try {
223
+ await deps.notifyUpdate({ command: pathParts[0] ?? "", flags, io });
224
+ } catch {
225
+ }
226
+ }
227
+ return code;
220
228
  }
221
229
 
222
230
  // src/commands/agent.ts
@@ -372,6 +380,12 @@ function resolveApiUrl() {
372
380
  if (env !== void 0 && env.length > 0) return env;
373
381
  return readCliConfig().apiUrl ?? DEFAULT_API_URL;
374
382
  }
383
+ var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
384
+ function resolveRegistryUrl() {
385
+ const env = process.env["npm_config_registry"];
386
+ if (env !== void 0 && env.length > 0) return env;
387
+ return DEFAULT_REGISTRY_URL;
388
+ }
375
389
 
376
390
  // src/diagnostics.ts
377
391
  var import_agent_core3 = require("@birdybeep/agent-core");
@@ -662,7 +676,7 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint)
662
676
  }
663
677
 
664
678
  // src/version.ts
665
- var CLI_VERSION = "0.1.0".length > 0 ? "0.1.0" : "0.0.0";
679
+ var CLI_VERSION = "0.2.0".length > 0 ? "0.2.0" : "0.0.0";
666
680
 
667
681
  // src/commands/pair.ts
668
682
  var DEFAULT_POLL_INTERVAL_MS = 2e3;
@@ -999,13 +1013,152 @@ function buildCommands() {
999
1013
  ];
1000
1014
  }
1001
1015
 
1016
+ // src/update-check.ts
1017
+ var import_node_fs3 = require("fs");
1018
+ var import_node_path2 = require("path");
1019
+ var import_agent_core13 = require("@birdybeep/agent-core");
1020
+ var PACKAGE_NAME = "@birdybeep/cli";
1021
+ var PACKAGE_PATH = "@birdybeep%2Fcli";
1022
+ var UPDATE_CACHE_FILE = "update-check.json";
1023
+ var DEFAULT_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1024
+ var DEFAULT_TIMEOUT_MS = 1500;
1025
+ var SKIP_COMMANDS = /* @__PURE__ */ new Set(["hook", "report-status"]);
1026
+ var SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
1027
+ function parseSemver(input) {
1028
+ const m = SEMVER_RE.exec(input.trim());
1029
+ if (m === null) return null;
1030
+ return {
1031
+ major: Number(m[1]),
1032
+ minor: Number(m[2]),
1033
+ patch: Number(m[3]),
1034
+ prerelease: m[4] !== void 0 ? m[4].split(".") : []
1035
+ };
1036
+ }
1037
+ function comparePrerelease(a, b) {
1038
+ if (a.length === 0 && b.length === 0) return 0;
1039
+ if (a.length === 0) return 1;
1040
+ if (b.length === 0) return -1;
1041
+ const len = Math.min(a.length, b.length);
1042
+ for (let i = 0; i < len; i++) {
1043
+ const ai = a[i];
1044
+ const bi = b[i];
1045
+ const aNum = /^\d+$/.test(ai);
1046
+ const bNum = /^\d+$/.test(bi);
1047
+ if (aNum && bNum) {
1048
+ const d = Number(ai) - Number(bi);
1049
+ if (d !== 0) return d < 0 ? -1 : 1;
1050
+ } else if (aNum) {
1051
+ return -1;
1052
+ } else if (bNum) {
1053
+ return 1;
1054
+ } else if (ai !== bi) {
1055
+ return ai < bi ? -1 : 1;
1056
+ }
1057
+ }
1058
+ if (a.length === b.length) return 0;
1059
+ return a.length < b.length ? -1 : 1;
1060
+ }
1061
+ function compareSemver(a, b) {
1062
+ if (a.major !== b.major) return a.major < b.major ? -1 : 1;
1063
+ if (a.minor !== b.minor) return a.minor < b.minor ? -1 : 1;
1064
+ if (a.patch !== b.patch) return a.patch < b.patch ? -1 : 1;
1065
+ return comparePrerelease(a.prerelease, b.prerelease);
1066
+ }
1067
+ function isNewer(current, latest) {
1068
+ const cur = parseSemver(current);
1069
+ const lat = parseSemver(latest);
1070
+ return cur !== null && lat !== null && compareSemver(cur, lat) < 0;
1071
+ }
1072
+ function updateCachePath() {
1073
+ return (0, import_node_path2.join)((0, import_agent_core13.birdyBeepConfigDir)(), UPDATE_CACHE_FILE);
1074
+ }
1075
+ function readUpdateCache() {
1076
+ try {
1077
+ const parsed = JSON.parse((0, import_node_fs3.readFileSync)(updateCachePath(), "utf8"));
1078
+ if (typeof parsed !== "object" || parsed === null) return null;
1079
+ const { checkedAt, latest } = parsed;
1080
+ if (typeof checkedAt !== "number") return null;
1081
+ if (latest !== null && typeof latest !== "string") return null;
1082
+ return { checkedAt, latest };
1083
+ } catch {
1084
+ return null;
1085
+ }
1086
+ }
1087
+ function writeUpdateCache(cache) {
1088
+ (0, import_node_fs3.mkdirSync)((0, import_agent_core13.birdyBeepConfigDir)(), { recursive: true, mode: 448 });
1089
+ (0, import_node_fs3.writeFileSync)(updateCachePath(), `${JSON.stringify(cache)}
1090
+ `, { mode: 384 });
1091
+ }
1092
+ async function fetchLatestVersion(registryUrl, fetchImpl, timeoutMs) {
1093
+ const url = `${registryUrl.replace(/\/+$/, "")}/${PACKAGE_PATH}/latest`;
1094
+ const controller = new AbortController();
1095
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
1096
+ if (typeof timer.unref === "function") timer.unref();
1097
+ try {
1098
+ const res = await fetchImpl(url, {
1099
+ headers: { accept: "application/json" },
1100
+ signal: controller.signal
1101
+ });
1102
+ if (!res.ok) throw new Error(`registry responded ${res.status}`);
1103
+ const body = await res.json();
1104
+ if (typeof body.version !== "string" || body.version.length === 0) {
1105
+ throw new Error("registry response had no version");
1106
+ }
1107
+ return body.version;
1108
+ } finally {
1109
+ clearTimeout(timer);
1110
+ }
1111
+ }
1112
+ function renderNotice(current, latest) {
1113
+ return `a new version of birdybeep is available: ${current} \u2192 ${latest}
1114
+ upgrade with: npm install -g ${PACKAGE_NAME}@latest`;
1115
+ }
1116
+ async function maybeNotifyUpdate(opts) {
1117
+ try {
1118
+ if (opts.command !== void 0 && SKIP_COMMANDS.has(opts.command)) return;
1119
+ if (opts.flags.json || opts.flags.nonInteractive) return;
1120
+ const env = opts.env ?? process.env;
1121
+ if (env["BIRDYBEEP_NO_UPDATE_NOTIFIER"] || env["NO_UPDATE_NOTIFIER"] || env["CI"]) return;
1122
+ const isTTY = opts.isTTY ?? Boolean(process.stderr.isTTY);
1123
+ if (!isTTY) return;
1124
+ const current = opts.currentVersion ?? CLI_VERSION;
1125
+ const now = opts.now ?? Date.now();
1126
+ const intervalMs = opts.intervalMs ?? DEFAULT_CHECK_INTERVAL_MS;
1127
+ const readCache = opts.readCache ?? readUpdateCache;
1128
+ const writeCache = opts.writeCache ?? writeUpdateCache;
1129
+ let cache = readCache();
1130
+ if (cache === null || now - cache.checkedAt >= intervalMs) {
1131
+ let latest = cache?.latest ?? null;
1132
+ try {
1133
+ latest = await fetchLatestVersion(
1134
+ opts.registryUrl ?? resolveRegistryUrl(),
1135
+ opts.fetchImpl ?? fetch,
1136
+ opts.timeoutMs ?? DEFAULT_TIMEOUT_MS
1137
+ );
1138
+ } catch {
1139
+ }
1140
+ cache = { checkedAt: now, latest };
1141
+ try {
1142
+ writeCache(cache);
1143
+ } catch {
1144
+ }
1145
+ }
1146
+ if (cache.latest !== null && isNewer(current, cache.latest)) {
1147
+ opts.io.errline(renderNotice(current, cache.latest));
1148
+ }
1149
+ } catch {
1150
+ }
1151
+ }
1152
+
1002
1153
  // src/cli.ts
1003
1154
  function runCli(argv, deps = {}) {
1155
+ const notifyUpdate = deps.updateCheck === false ? void 0 : (ctx) => maybeNotifyUpdate({ ...ctx, ...deps.updateCheck ?? {} });
1004
1156
  return dispatch(argv, {
1005
1157
  version: CLI_VERSION,
1006
1158
  commands: deps.commands ?? buildCommands(),
1007
1159
  stdout: deps.stdout ?? process.stdout,
1008
1160
  stderr: deps.stderr ?? process.stderr,
1161
+ ...notifyUpdate !== void 0 ? { notifyUpdate } : {},
1009
1162
  ...deps.ensureConfig !== void 0 ? { ensureConfig: deps.ensureConfig } : {}
1010
1163
  });
1011
1164
  }