quiver-cli 0.2.0 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +15 -1
  2. package/dist/cli.js +135 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -57,7 +57,7 @@ quiver-cli check # detect drift (CI-friendly: --json, exit 1)
57
57
  | `quiver-cli login` | Store a GitHub token for remote (`github:`) catalogs |
58
58
  | `quiver-cli logout` | Remove the stored GitHub token |
59
59
  | `quiver-cli help` | Show help |
60
- | `quiver-cli version` | Show the version (`-v`, `--version`) |
60
+ | `quiver-cli version` | Show the version + any available update (`-v`, `--version`) |
61
61
 
62
62
  Options: `-f/--force`, `--all/-y` (non-interactive), `--json`
63
63
  (status/check/upstream/list), `--providers=claude,opencode` (limit generated
@@ -183,6 +183,20 @@ exactly the variables the selected servers need (none needed → no file). For
183
183
  Codex, secret headers are mapped to `env_http_headers` so secrets never land in
184
184
  the committed `config.toml`.
185
185
 
186
+ ## Staying up to date
187
+
188
+ quiver-cli checks npm for a newer release (at most once a day, cached under
189
+ `~/.cache/quiver/`) and prints a one-line notice after a command when an update
190
+ is available. `quiver-cli version` checks on demand. Update with:
191
+
192
+ ```bash
193
+ pnpm add -g quiver-cli # or: npm i -g quiver-cli / yarn global add quiver-cli
194
+ ```
195
+
196
+ The check is best-effort and never blocks or fails a command. It is silenced
197
+ automatically for `--json`, non-interactive shells and CI, and can be disabled
198
+ entirely with `QUIVER_NO_UPDATE_NOTIFIER=1`.
199
+
186
200
  ## Development
187
201
 
188
202
  ```bash
package/dist/cli.js CHANGED
@@ -766,13 +766,6 @@ var init_remote = __esm({
766
766
  });
767
767
 
768
768
  // src/catalog/resolve.ts
769
- var resolve_exports = {};
770
- __export(resolve_exports, {
771
- DEFAULT_CATALOG_SOURCE: () => DEFAULT_CATALOG_SOURCE,
772
- isCatalogWritable: () => isCatalogWritable,
773
- packageRoot: () => packageRoot,
774
- resolveCatalog: () => resolveCatalog
775
- });
776
769
  import { accessSync, constants, existsSync as existsSync6 } from "fs";
777
770
  import { dirname as dirname3, relative as relative3, resolve as resolve7, sep } from "path";
778
771
  import { fileURLToPath } from "url";
@@ -2978,6 +2971,114 @@ var init_logout = __esm({
2978
2971
  }
2979
2972
  });
2980
2973
 
2974
+ // src/version/notifier.ts
2975
+ var notifier_exports = {};
2976
+ __export(notifier_exports, {
2977
+ checkForUpdate: () => checkForUpdate,
2978
+ compareSemver: () => compareSemver,
2979
+ getCurrentVersion: () => getCurrentVersion,
2980
+ installHint: () => installHint,
2981
+ notifierSuppressed: () => notifierSuppressed
2982
+ });
2983
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
2984
+ import { homedir as homedir3 } from "os";
2985
+ import { dirname as dirname6, resolve as resolve19 } from "path";
2986
+ var REGISTRY_URL, CHECK_TTL_MS, FETCH_TIMEOUT_MS, INSTALL_HINT, cacheFilePath, installHint, getCurrentVersion, compareSemver, readCache, writeCache, fetchLatestVersion, checkForUpdate, notifierSuppressed;
2987
+ var init_notifier = __esm({
2988
+ "src/version/notifier.ts"() {
2989
+ "use strict";
2990
+ init_resolve();
2991
+ REGISTRY_URL = "https://registry.npmjs.org/quiver-cli/latest";
2992
+ CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
2993
+ FETCH_TIMEOUT_MS = 2e3;
2994
+ INSTALL_HINT = "pnpm add -g quiver-cli";
2995
+ cacheFilePath = () => {
2996
+ const base = process.env["XDG_CACHE_HOME"] || resolve19(homedir3(), ".cache");
2997
+ return resolve19(base, "quiver", "update-check.json");
2998
+ };
2999
+ installHint = () => INSTALL_HINT;
3000
+ getCurrentVersion = () => {
3001
+ try {
3002
+ const pkg = JSON.parse(
3003
+ readFileSync11(resolve19(packageRoot, "package.json"), "utf8")
3004
+ );
3005
+ return pkg.version;
3006
+ } catch {
3007
+ return "0.0.0";
3008
+ }
3009
+ };
3010
+ compareSemver = (a, b) => {
3011
+ const parse2 = (v) => {
3012
+ const [core = "", ...preParts] = v.replace(/^v/, "").split("-");
3013
+ const nums = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
3014
+ while (nums.length < 3) nums.push(0);
3015
+ return { nums, pre: preParts.length > 0 };
3016
+ };
3017
+ const pa = parse2(a);
3018
+ const pb = parse2(b);
3019
+ for (let i = 0; i < 3; i += 1) {
3020
+ const da = pa.nums[i] ?? 0;
3021
+ const db = pb.nums[i] ?? 0;
3022
+ if (da !== db) return da > db ? 1 : -1;
3023
+ }
3024
+ if (pa.pre !== pb.pre) return pa.pre ? -1 : 1;
3025
+ return 0;
3026
+ };
3027
+ readCache = () => {
3028
+ const path = cacheFilePath();
3029
+ if (!existsSync14(path)) return null;
3030
+ try {
3031
+ return JSON.parse(readFileSync11(path, "utf8"));
3032
+ } catch {
3033
+ return null;
3034
+ }
3035
+ };
3036
+ writeCache = (cache) => {
3037
+ try {
3038
+ const path = cacheFilePath();
3039
+ mkdirSync7(dirname6(path), { recursive: true });
3040
+ writeFileSync9(path, JSON.stringify(cache, null, 2) + "\n");
3041
+ } catch {
3042
+ }
3043
+ };
3044
+ fetchLatestVersion = async () => {
3045
+ const controller = new AbortController();
3046
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
3047
+ try {
3048
+ const res = await fetch(REGISTRY_URL, {
3049
+ signal: controller.signal,
3050
+ headers: { Accept: "application/json" }
3051
+ });
3052
+ if (!res.ok) return null;
3053
+ const body = await res.json();
3054
+ return typeof body.version === "string" ? body.version : null;
3055
+ } catch {
3056
+ return null;
3057
+ } finally {
3058
+ clearTimeout(timer);
3059
+ }
3060
+ };
3061
+ checkForUpdate = async (options = {}) => {
3062
+ const current = getCurrentVersion();
3063
+ const cache = readCache();
3064
+ let latest = cache?.latest ?? null;
3065
+ const fresh = cache && Date.now() - new Date(cache.checkedAt).getTime() < CHECK_TTL_MS;
3066
+ if (options.force || !fresh) {
3067
+ const fetched = await fetchLatestVersion();
3068
+ if (fetched) {
3069
+ latest = fetched;
3070
+ writeCache({ checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latest: fetched });
3071
+ } else if (!cache) {
3072
+ writeCache({ checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latest: null });
3073
+ }
3074
+ }
3075
+ const updateAvailable = latest !== null && compareSemver(latest, current) > 0;
3076
+ return { current, latest, updateAvailable };
3077
+ };
3078
+ notifierSuppressed = (json) => Boolean(process.env["QUIVER_NO_UPDATE_NOTIFIER"]) || Boolean(process.env["CI"]) || !process.stdout.isTTY || json;
3079
+ }
3080
+ });
3081
+
2981
3082
  // src/cli.ts
2982
3083
  init_prompts();
2983
3084
  import process4 from "process";
@@ -3102,13 +3203,14 @@ var run = async () => {
3102
3203
  case "version":
3103
3204
  case "--version":
3104
3205
  case "-v": {
3105
- const { readFileSync: readFileSync11 } = await import("fs");
3106
- const { resolve: resolve19 } = await import("path");
3107
- const { packageRoot: packageRoot2 } = await Promise.resolve().then(() => (init_resolve(), resolve_exports));
3108
- const pkg = JSON.parse(
3109
- readFileSync11(resolve19(packageRoot2, "package.json"), "utf8")
3110
- );
3111
- console.log(pkg.version);
3206
+ const { checkForUpdate: checkForUpdate2, installHint: installHint2 } = await Promise.resolve().then(() => (init_notifier(), notifier_exports));
3207
+ const info2 = await checkForUpdate2({ force: true });
3208
+ console.log(info2.current);
3209
+ if (info2.updateAvailable) {
3210
+ console.log(
3211
+ `update available: ${info2.latest} (run: ${installHint2()})`
3212
+ );
3213
+ }
3112
3214
  break;
3113
3215
  }
3114
3216
  default:
@@ -3117,6 +3219,25 @@ var run = async () => {
3117
3219
  console.log(HELP);
3118
3220
  process4.exitCode = 1;
3119
3221
  }
3222
+ if (!["version", "--version", "-v", "help", "--help", "-h"].includes(command)) {
3223
+ await maybeNotifyUpdate(options.json);
3224
+ }
3225
+ };
3226
+ var maybeNotifyUpdate = async (json) => {
3227
+ try {
3228
+ const { checkForUpdate: checkForUpdate2, notifierSuppressed: notifierSuppressed2, installHint: installHint2 } = await Promise.resolve().then(() => (init_notifier(), notifier_exports));
3229
+ if (notifierSuppressed2(json)) return;
3230
+ const info2 = await checkForUpdate2();
3231
+ if (!info2.updateAvailable) return;
3232
+ const c = palette();
3233
+ console.log(
3234
+ `
3235
+ ${c.yellow("\u25B2")} ${c.bold("update available")} ${c.dim(
3236
+ info2.current
3237
+ )} \u2192 ${c.cyan(info2.latest)} ${c.dim(`run: ${installHint2()}`)}`
3238
+ );
3239
+ } catch {
3240
+ }
3120
3241
  };
3121
3242
  run().catch(async (err) => {
3122
3243
  await error(err instanceof Error ? err.message : String(err));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quiver-cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Compose a selected subset of skills, commands & MCP servers from a central catalog into any repo as native configs for opencode, Claude Code and Codex - with lockfile-based drift awareness.",
5
5
  "type": "module",
6
6
  "bin": {