ymmv-cli 0.8.0 → 0.9.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 +16 -4
  2. package/dist/cli.js +309 -15
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -9,10 +9,13 @@ clean page at `ymmv.fyi/<handle>` in about 10 seconds. See a live one:
9
9
  ![Running npx ymmv-cli to detect, confirm, and publish a dev stack to a live ymmv.fyi page](https://raw.githubusercontent.com/ymmv-fyi/ymmv/main/docs/demo.gif)
10
10
 
11
11
  ```sh
12
- npx ymmv-cli # detect your stack, confirm, go live at ymmv.fyi/<you>
13
- npx ymmv-cli bardisty # view someone's stack in the terminal
12
+ npx ymmv-cli@latest # detect your stack, confirm, go live at ymmv.fyi/<you>
13
+ npx ymmv-cli@latest bardisty # view someone's stack in the terminal
14
14
  ```
15
15
 
16
+ (`@latest` makes npx look for the newest release; without a version spec, npx can pin you
17
+ to whatever it downloaded first.)
18
+
16
19
  Viewing someone while you're logged in diffs their stack against yours:
17
20
 
18
21
  ```
@@ -33,6 +36,9 @@ Install once for the short `ymmv` command:
33
36
  npm i -g ymmv-cli
34
37
  ```
35
38
 
39
+ When a newer release exists, the CLI mentions it after a command (interactive terminals only)
40
+ and `ymmv update` upgrades a global install in place.
41
+
36
42
  First run includes a one-time GitHub sign-in. Works on macOS, Linux, Windows,
37
43
  and WSL. Nothing is published until you confirm (detection only pre-fills), and
38
44
  `ymmv delete` removes everything. Releases are published from GitHub Actions
@@ -47,7 +53,10 @@ via npm Trusted Publishing, with provenance.
47
53
  - `ymmv unset editor` removes one value (`ymmv set editor -` works too); `ymmv unset --extra "Keyboard"` removes an extra
48
54
  - `ymmv delete` removes your profile (`ymmv delete -y` skips the confirm, for scripts)
49
55
  - `ymmv login` / `ymmv logout` sign in / out
50
- - `ymmv version` prints the CLI version
56
+ - `ymmv update` updates the CLI to the latest release (runs the matching upgrade for npm, pnpm,
57
+ and bun global installs; via npx it prints the invocation to use instead; anything else gets
58
+ the commands to run by hand)
59
+ - `ymmv version` prints the CLI version (and notes a newer release when one is known)
51
60
 
52
61
  Values are capped at 256 characters and extra labels at 64; a profile holds up
53
62
  to 32 extras.
@@ -68,6 +77,9 @@ Every profile is open JSON too: `GET https://ymmv.fyi/api/v1/u/<handle>`. Full c
68
77
  - `YMMV_HANDLE` names the GitHub username `YMMV_TOKEN` belongs to. Required for `ymmv -y` and
69
78
  `ymmv set`/`unset` under an env token (there is no server lookup for it); ignored without
70
79
  `YMMV_TOKEN`.
80
+ - `YMMV_NO_UPDATE_CHECK` disables the startup check for newer releases (the ecosystem-standard
81
+ `NO_UPDATE_NOTIFIER` works too). The check is also off automatically under `CI`, in pipes,
82
+ and in dev builds; it never blocks or fails a command.
71
83
 
72
84
  ## Publishing from CI
73
85
 
@@ -78,7 +90,7 @@ Every profile is open JSON too: `GET https://ymmv.fyi/api/v1/u/<handle>`. Full c
78
90
  `~/.config/ymmv/token.json` (Linux), `~/Library/Preferences/ymmv/token.json` (macOS),
79
91
  `%APPDATA%\ymmv\Config\token.json` (Windows).
80
92
  3. Set it as a CI secret named `YMMV_TOKEN`, and set `YMMV_HANDLE` to your GitHub username.
81
- 4. Run `npx ymmv-cli -y` in the job.
93
+ 4. Run `npx ymmv-cli@latest -y` in the job.
82
94
 
83
95
  Two things to know:
84
96
 
package/dist/cli.js CHANGED
@@ -349,7 +349,8 @@ var CLI_VERBS = [
349
349
  "view",
350
350
  "help",
351
351
  "publish",
352
- "version"
352
+ "version",
353
+ "update"
353
354
  ];
354
355
  var RESERVED = [.../* @__PURE__ */ new Set([...RESERVED_ROUTES, ...CLI_VERBS])];
355
356
  var RESERVED_SET = new Set(RESERVED);
@@ -417,10 +418,10 @@ function isHttpUrl(value) {
417
418
  return HTTP_URL_RE.test(value.trim());
418
419
  }
419
420
  var NO_OSC8_TERMS = /* @__PURE__ */ new Set(["linux", "dumb"]);
420
- function link(url, color, term = process.env.TERM) {
421
+ function link(url, color, term = process.env.TERM, label) {
421
422
  const clean = sanitizeValue(url).trim();
422
423
  if (!color) return clean;
423
- const text = `${CODES.amber}${displayUrl(clean)}${CODES.reset}`;
424
+ const text = `${CODES.amber}${label ? sanitizeValue(label) : displayUrl(clean)}${CODES.reset}`;
424
425
  if (term !== void 0 && NO_OSC8_TERMS.has(term)) return text;
425
426
  return `${OSC}8;;${clean}${ST}${text}${OSC}8;;${ST}`;
426
427
  }
@@ -539,7 +540,7 @@ function nudge(color) {
539
540
  function notFound(handle, color, base) {
540
541
  return `
541
542
  no ymmv profile for "${sanitizeValue(handle)}" yet.
542
- publish one at ${link(base, color)} with: npx ymmv-cli`;
543
+ publish one at ${link(base, color)} with: npx ymmv-cli@latest`;
543
544
  }
544
545
 
545
546
  // src/http.ts
@@ -626,9 +627,6 @@ async function safeFetch(url, init, reach, fetchFn = globalThis.fetch) {
626
627
  }
627
628
  }
628
629
 
629
- // src/index.ts
630
- import { readFileSync as readFileSync2 } from "fs";
631
-
632
630
  // src/config.ts
633
631
  function normalizeBase(raw) {
634
632
  return raw.replace(/\/+$/, "");
@@ -1827,7 +1825,7 @@ function resolveArg(argv) {
1827
1825
  }
1828
1826
  return { kind: "publish", yes: true };
1829
1827
  }
1830
- if (first === "login" || first === "logout") return noArgs(first, rest);
1828
+ if (first === "login" || first === "logout" || first === "update") return noArgs(first, rest);
1831
1829
  if (first === "publish") {
1832
1830
  return yesOnly("usage: ymmv publish [-y]", rest, (yes) => ({ kind: "publish", yes }));
1833
1831
  }
@@ -1877,6 +1875,274 @@ function reservedError(handle, hintVerbs = false) {
1877
1875
  };
1878
1876
  }
1879
1877
 
1878
+ // src/update.ts
1879
+ import { spawn as nodeSpawn } from "child_process";
1880
+ import { homedir } from "os";
1881
+
1882
+ // src/update-check.ts
1883
+ import { randomUUID as randomUUID2 } from "crypto";
1884
+ import { readFileSync as readFileSync2, realpathSync } from "fs";
1885
+ import { mkdir as mkdir2, readFile as readFile2, rename as rename2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
1886
+ import { dirname as dirname2, join as join2 } from "path";
1887
+ import envPaths2 from "env-paths";
1888
+ var REGISTRY_URL = "https://registry.npmjs.org/ymmv-cli/latest";
1889
+ var RELEASES_URL = "https://github.com/ymmv-fyi/ymmv/releases";
1890
+ var FETCH_TIMEOUT_MS = 2e3;
1891
+ var FINISH_GRACE_MS = 500;
1892
+ var SUCCESS_TTL_MS = 24 * 60 * 60 * 1e3;
1893
+ var FAILURE_TTL_MS = 6 * 60 * 60 * 1e3;
1894
+ var VERSION_FRESH_MS = 7 * 24 * 60 * 60 * 1e3;
1895
+ function ownVersion() {
1896
+ try {
1897
+ const pkg = JSON.parse(readFileSync2(new URL("../package.json", import.meta.url), "utf8"));
1898
+ return typeof pkg.version === "string" ? pkg.version : null;
1899
+ } catch {
1900
+ return null;
1901
+ }
1902
+ }
1903
+ function parseTriple(v) {
1904
+ const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v);
1905
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
1906
+ }
1907
+ function isNewer(latest, current) {
1908
+ const l = parseTriple(latest);
1909
+ const c = parseTriple(current);
1910
+ if (!l || !c) return false;
1911
+ for (let i = 0; i < 3; i++) {
1912
+ if (l[i] !== c[i]) return l[i] > c[i];
1913
+ }
1914
+ return false;
1915
+ }
1916
+ function updateCachePath() {
1917
+ return join2(envPaths2("ymmv", { suffix: "" }).config, "update-check.json");
1918
+ }
1919
+ async function readCache(path, now) {
1920
+ try {
1921
+ const parsed = JSON.parse(await readFile2(path, "utf8"));
1922
+ if (typeof parsed !== "object" || parsed === null) return null;
1923
+ const c = parsed;
1924
+ if (typeof c.lastCheckedAt !== "number" || typeof c.ok !== "boolean") return null;
1925
+ if (!Number.isFinite(c.lastCheckedAt) || c.lastCheckedAt > now) return null;
1926
+ if (c.latest !== void 0 && typeof c.latest !== "string") return null;
1927
+ if (c.latestAt !== void 0 && (!Number.isFinite(c.latestAt) || c.latestAt > now)) return null;
1928
+ return { lastCheckedAt: c.lastCheckedAt, ok: c.ok, latest: c.latest, latestAt: c.latestAt };
1929
+ } catch {
1930
+ return null;
1931
+ }
1932
+ }
1933
+ async function writeCache(path, cache) {
1934
+ const tmp = `${path}.${randomUUID2()}.tmp`;
1935
+ try {
1936
+ await mkdir2(dirname2(path), { recursive: true });
1937
+ await writeFile2(tmp, JSON.stringify(cache));
1938
+ await rename2(tmp, path);
1939
+ } catch {
1940
+ await rm2(tmp, { force: true }).catch(() => {
1941
+ });
1942
+ }
1943
+ }
1944
+ function binRealpath() {
1945
+ const raw = process.argv[1];
1946
+ if (!raw) return void 0;
1947
+ try {
1948
+ return realpathSync(raw);
1949
+ } catch {
1950
+ return raw;
1951
+ }
1952
+ }
1953
+ var NPM_PREFIX_ROOTS = /* @__PURE__ */ new Set(["usr", "local", "homebrew", "nodejs", "node", "installation"]);
1954
+ var VERSIONISH_RE = /^v?\d+(\.\d+)*$/;
1955
+ function detectInstallMethod(binPath) {
1956
+ if (!binPath) return "unknown";
1957
+ const segs = binPath.split(/[\\/]+/).filter(Boolean).map((s) => s.toLowerCase());
1958
+ const has = (name) => segs.includes(name);
1959
+ if (has("_npx")) return "ephemeral";
1960
+ if (has("dlx") || segs.some((s) => s.startsWith("dlx-"))) return "ephemeral";
1961
+ if (segs.some((s) => s.startsWith("bunx-"))) return "ephemeral";
1962
+ if (has(".bun") && has("cache")) return "ephemeral";
1963
+ if (has(".bun") && (has("global") || has("bin"))) return "bun-global";
1964
+ if (has(".volta")) return "unknown";
1965
+ if (segs.some((s, i) => s === "pnpm" && segs[i + 1] === "global")) return "pnpm-global";
1966
+ if (has(".pnpm")) return "unknown";
1967
+ const npmGlobal = segs.some(
1968
+ (s, i) => s === "node_modules" && segs[i + 1] === "ymmv-cli" && (segs[i - 1] === "lib" && i >= 2 && (NPM_PREFIX_ROOTS.has(segs[i - 2]) || VERSIONISH_RE.test(segs[i - 2])) || segs[i - 1] === "npm")
1969
+ );
1970
+ if (npmGlobal) return "npm-global";
1971
+ return "unknown";
1972
+ }
1973
+ function npxInvocation(latest) {
1974
+ const triple = latest ? parseTriple(latest) : null;
1975
+ return `npx ymmv-cli@${triple ? triple.join(".") : "latest"}`;
1976
+ }
1977
+ function formatUpdateNotice(current, latest, method, color, term) {
1978
+ const cur = parseTriple(current);
1979
+ const lat = parseTriple(latest);
1980
+ if (!cur || !lat || !isNewer(lat.join("."), cur.join("."))) return null;
1981
+ const latestClean = lat.join(".");
1982
+ const currentClean = cur.join(".");
1983
+ const cmd = method === "ephemeral" ? npxInvocation(latestClean) : "ymmv update";
1984
+ const c = palette(color);
1985
+ const headline = `ymmv-cli ${latestClean} is out`;
1986
+ const linked = color ? link(RELEASES_URL, true, term, headline) : headline;
1987
+ return `${linked} ${c.faint}(you have ${currentClean})${c.reset} \u2192 run ${c.bold}${cmd}${c.reset}`;
1988
+ }
1989
+ function updatesOptedOut(env) {
1990
+ if (env.YMMV_NO_UPDATE_CHECK) return true;
1991
+ if (env.NO_UPDATE_NOTIFIER) return true;
1992
+ if (env.CI && env.CI !== "false") return true;
1993
+ return false;
1994
+ }
1995
+ async function readCachedLatest(deps = {}) {
1996
+ const now = (deps.now ?? Date.now)();
1997
+ const cache = await readCache(deps.cachePath ?? updateCachePath(), now);
1998
+ if (!cache?.latest || cache.latestAt === void 0) return null;
1999
+ if (now - cache.latestAt > VERSION_FRESH_MS) return null;
2000
+ const triple = parseTriple(cache.latest);
2001
+ return triple ? triple.join(".") : null;
2002
+ }
2003
+ var GRACE = /* @__PURE__ */ Symbol("grace");
2004
+ function startUpdateCheck(deps = {}) {
2005
+ const env = deps.env ?? process.env;
2006
+ const nowFn = deps.now ?? Date.now;
2007
+ const stderrIsTTY = deps.stderrIsTTY ?? Boolean(process.stderr.isTTY);
2008
+ const inert = { finish: async () => null, abort: () => {
2009
+ } };
2010
+ if (updatesOptedOut(env)) return inert;
2011
+ if (!stderrIsTTY) return inert;
2012
+ const current = deps.currentVersion !== void 0 ? deps.currentVersion : ownVersion();
2013
+ const currentTriple = current === null ? null : parseTriple(current);
2014
+ if (!currentTriple || currentTriple.join(".") === "0.0.0") return inert;
2015
+ const currentClean = currentTriple.join(".");
2016
+ const cachePath = deps.cachePath ?? updateCachePath();
2017
+ const doFetch = deps.fetch ?? globalThis.fetch;
2018
+ const method = detectInstallMethod(deps.binPath ?? binRealpath());
2019
+ const color = useColor(env, stderrIsTTY);
2020
+ const term = env.TERM;
2021
+ const controller = new AbortController();
2022
+ const notice = (latest) => latest ? formatUpdateNotice(currentClean, latest, method, color, term) : null;
2023
+ const check = (async () => {
2024
+ const cache = await readCache(cachePath, nowFn());
2025
+ if (cache && nowFn() - cache.lastCheckedAt < (cache.ok ? SUCCESS_TTL_MS : FAILURE_TTL_MS)) {
2026
+ return notice(cache.latest);
2027
+ }
2028
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
2029
+ timeout.unref?.();
2030
+ let fetched = null;
2031
+ try {
2032
+ const res = await doFetch(REGISTRY_URL, {
2033
+ signal: controller.signal,
2034
+ headers: { accept: "application/json" }
2035
+ });
2036
+ if (res.ok) {
2037
+ const body = await res.json();
2038
+ if (typeof body.version === "string") fetched = body.version;
2039
+ }
2040
+ } catch {
2041
+ } finally {
2042
+ clearTimeout(timeout);
2043
+ }
2044
+ const triple = fetched === null ? null : parseTriple(fetched);
2045
+ const canonical2 = triple ? triple.join(".") : void 0;
2046
+ const now = nowFn();
2047
+ await writeCache(cachePath, {
2048
+ lastCheckedAt: now,
2049
+ ok: canonical2 !== void 0,
2050
+ latest: canonical2 ?? cache?.latest,
2051
+ latestAt: canonical2 !== void 0 ? now : cache?.latestAt
2052
+ });
2053
+ return notice(canonical2 ?? cache?.latest);
2054
+ })().catch(() => null);
2055
+ return {
2056
+ async finish() {
2057
+ const result = await new Promise((resolve) => {
2058
+ const timer = setTimeout(() => resolve(GRACE), FINISH_GRACE_MS);
2059
+ timer.unref?.();
2060
+ void check.then((v) => {
2061
+ clearTimeout(timer);
2062
+ resolve(v);
2063
+ });
2064
+ });
2065
+ if (result === GRACE) {
2066
+ controller.abort();
2067
+ return null;
2068
+ }
2069
+ return result;
2070
+ },
2071
+ abort() {
2072
+ controller.abort();
2073
+ }
2074
+ };
2075
+ }
2076
+
2077
+ // src/update.ts
2078
+ var UPGRADE_COMMANDS = {
2079
+ "npm-global": "npm i -g ymmv-cli@latest",
2080
+ "pnpm-global": "pnpm add -g ymmv-cli@latest",
2081
+ "bun-global": "bun add -g ymmv-cli@latest"
2082
+ };
2083
+ var MANUAL_LIST = Object.values(UPGRADE_COMMANDS).map((cmd) => ` ${cmd}`).join("\n");
2084
+ var NOT_FOUND_CODES = /* @__PURE__ */ new Set([127, 9009]);
2085
+ function manualFallback(intro) {
2086
+ return `${intro} Update with the command that matches your setup:
2087
+ ${MANUAL_LIST}`;
2088
+ }
2089
+ async function runUpdate(deps = {}) {
2090
+ const method = detectInstallMethod(deps.binPath ?? binRealpath());
2091
+ const c = palette(colorEnabled());
2092
+ if (method === "ephemeral") {
2093
+ const cached = await readCachedLatest({ now: deps.now, cachePath: deps.cachePath });
2094
+ const current = deps.currentVersion !== void 0 ? deps.currentVersion : ownVersion();
2095
+ const pin = cached !== null && current !== null && isNewer(cached, current) ? cached : null;
2096
+ console.log(
2097
+ message(
2098
+ `This run came from a package runner cache (npx-style), so there is no install to update.
2099
+ Next time run: ${c.bold}${npxInvocation(pin)}${c.reset}`
2100
+ )
2101
+ );
2102
+ return;
2103
+ }
2104
+ if (method === "unknown") {
2105
+ console.log(message(manualFallback("Couldn't tell how ymmv-cli was installed.")));
2106
+ return;
2107
+ }
2108
+ const cmd = UPGRADE_COMMANDS[method];
2109
+ const doSpawn = deps.spawn ?? nodeSpawn;
2110
+ const failSpawn = () => {
2111
+ console.error(message(manualFallback(`Couldn't run \`${cmd}\`.`)));
2112
+ process.exitCode = 1;
2113
+ };
2114
+ console.log(message(`${c.faint}running${c.reset} ${c.bold}${cmd}${c.reset}`));
2115
+ await new Promise((resolve) => {
2116
+ let child;
2117
+ try {
2118
+ child = doSpawn(cmd, { stdio: "inherit", shell: true, cwd: homedir() });
2119
+ } catch {
2120
+ failSpawn();
2121
+ resolve();
2122
+ return;
2123
+ }
2124
+ let settled = false;
2125
+ const settle = (fn) => {
2126
+ if (settled) return;
2127
+ settled = true;
2128
+ fn();
2129
+ resolve();
2130
+ };
2131
+ child.on("error", () => {
2132
+ settle(failSpawn);
2133
+ });
2134
+ child.on("exit", (code) => {
2135
+ settle(() => {
2136
+ if (code !== null && NOT_FOUND_CODES.has(code)) {
2137
+ failSpawn();
2138
+ } else if (code) {
2139
+ process.exitCode = code;
2140
+ }
2141
+ });
2142
+ });
2143
+ });
2144
+ }
2145
+
1880
2146
  // src/index.ts
1881
2147
  var help = (c) => `${c.bold}ymmv${c.reset}: terminal-native developer tool-stack profiles (ymmv.fyi)
1882
2148
 
@@ -1891,6 +2157,7 @@ ${c.faint}Usage:${c.reset}
1891
2157
  ymmv unset --extra "L" remove a free-form extra
1892
2158
  ymmv delete [-y] delete your profile (permanent; -y skips the confirm)
1893
2159
  ymmv login | logout GitHub device-flow auth
2160
+ ymmv update update ymmv-cli to the latest release
1894
2161
  ymmv help | version
1895
2162
 
1896
2163
  ${c.faint}Curated keys:${c.reset} editor, os, shell, prompt, terminal, browser, window-manager,
@@ -1922,12 +2189,21 @@ async function logout() {
1922
2189
  console.log(message(revoked ? "Logged out." : "Logged out (no active session on this server)."));
1923
2190
  }
1924
2191
  function printVersion() {
1925
- try {
1926
- const pkg = JSON.parse(readFileSync2(new URL("../package.json", import.meta.url), "utf8"));
1927
- console.log(`ymmv-cli ${pkg.version ?? "unknown"}`);
1928
- } catch {
1929
- console.log("ymmv-cli (version unknown)");
1930
- }
2192
+ const version = ownVersion();
2193
+ console.log(version === null ? "ymmv-cli (version unknown)" : `ymmv-cli ${version}`);
2194
+ }
2195
+ async function printLatestHint() {
2196
+ if (!process.stderr.isTTY) return;
2197
+ if (updatesOptedOut(process.env)) return;
2198
+ const current = ownVersion();
2199
+ const currentTriple = current === null ? null : parseTriple(current);
2200
+ if (!currentTriple || currentTriple.join(".") === "0.0.0") return;
2201
+ const latest = await readCachedLatest();
2202
+ if (latest === null || !isNewer(latest, currentTriple.join("."))) return;
2203
+ const c = palette(useColor(process.env, true));
2204
+ console.error(
2205
+ message(`${c.faint}latest: ${latest}${c.reset} \u2192 run ${c.bold}ymmv update${c.reset}`)
2206
+ );
1931
2207
  }
1932
2208
  async function interactive(run, yes) {
1933
2209
  const isTTY = Boolean(process.stdin.isTTY);
@@ -1938,9 +2214,12 @@ async function interactive(run, yes) {
1938
2214
  prompter?.close();
1939
2215
  }
1940
2216
  }
2217
+ function wantsUpdateCheck(kind) {
2218
+ return kind !== "help" && kind !== "version" && kind !== "error" && kind !== "update";
2219
+ }
1941
2220
  async function main(argv) {
1942
2221
  const cmd = resolveArg(argv);
1943
- if (cmd.kind !== "logout") {
2222
+ if (cmd.kind !== "logout" && cmd.kind !== "update") {
1944
2223
  const problem = baseProblem() ?? credentialEnvProblem();
1945
2224
  if (problem) {
1946
2225
  console.error(message(problem));
@@ -1948,6 +2227,17 @@ async function main(argv) {
1948
2227
  return;
1949
2228
  }
1950
2229
  }
2230
+ const update = wantsUpdateCheck(cmd.kind) ? startUpdateCheck() : null;
2231
+ try {
2232
+ await dispatch(cmd);
2233
+ } catch (e) {
2234
+ update?.abort();
2235
+ throw e;
2236
+ }
2237
+ const notice = await update?.finish();
2238
+ if (notice) console.error(message(notice));
2239
+ }
2240
+ async function dispatch(cmd) {
1951
2241
  switch (cmd.kind) {
1952
2242
  case "publish":
1953
2243
  await interactive(publish, cmd.yes);
@@ -1980,11 +2270,15 @@ async function main(argv) {
1980
2270
  );
1981
2271
  }
1982
2272
  break;
2273
+ case "update":
2274
+ await runUpdate();
2275
+ break;
1983
2276
  case "help":
1984
2277
  console.log(help(palette(colorEnabled())));
1985
2278
  break;
1986
2279
  case "version":
1987
2280
  printVersion();
2281
+ await printLatestHint();
1988
2282
  break;
1989
2283
  case "error":
1990
2284
  console.error(message(cmd.message));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ymmv-cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Publish and diff terminal-native developer tool-stack profiles at ymmv.fyi.",
5
5
  "type": "module",
6
6
  "bin": {