usagemax 0.3.8 → 0.3.9

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/README.md CHANGED
@@ -40,10 +40,12 @@ bunx usagemax sync --dry-run --explain
40
40
  bunx usagemax sync
41
41
  ```
42
42
 
43
- The CLI checks npm's `latest` dist-tag at most twice per day and never replaces
44
- itself silently. Run `usagemax update sync` to hand a command to the current
45
- release without typing `@latest`, or set `USAGEMAX_AUTO_UPDATE=1` for an
46
- explicit automatic handoff. Use `--no-update-check` or set
43
+ The CLI checks npm's `latest` dist-tag at most twice per day. Run
44
+ `usagemax update` to update a global installation through the package manager
45
+ that launched it (Bun or npm). Add `--check` for a read-only check, or
46
+ `--json` for automation. A command suffix such as `usagemax update sync` hands
47
+ that command to the newest release without typing `@latest`. Set
48
+ `USAGEMAX_AUTO_UPDATE=1` for an explicit automatic handoff. Use `--no-update-check` or set
47
49
  `USAGEMAX_DISABLE_UPDATE_CHECK=1` in offline environments. Interactive
48
50
  terminals show a small stderr progress line; JSON,
49
51
  quiet, CI, and scheduled runs remain machine-readable and quiet.
@@ -81,6 +83,9 @@ usagemax token status Diagnose a key piped on stdin
81
83
  usagemax service install Opt into periodic OS checkpoints
82
84
  usagemax service status|run|uninstall
83
85
  usagemax unlink [--revoke] Remove local credentials
86
+ usagemax update Update the global CLI through Bun or npm
87
+ usagemax update --check Check without installing
88
+ usagemax update sync Run sync through the newest CLI release
84
89
  ```
85
90
 
86
91
  Useful options:
@@ -93,7 +98,8 @@ bunx usagemax status --remote --json # remote check; secret is never printe
93
98
  bunx usagemax doctor --deep --json # parse and audit retained history
94
99
  bunx usagemax link UMX-… --no-sync # link without uploading yet
95
100
  bunx usagemax --version --json # print CLI and ccusage versions
96
- bunx usagemax update --json # machine-readable release check
101
+ bunx usagemax update --json # update and return machine-readable status
102
+ bunx usagemax update --check --json # check without installing
97
103
  bunx usagemax unlink --json # safe automation result; no secret output
98
104
  ```
99
105
 
@@ -135,7 +141,7 @@ printf '%s' "$USAGEMAX_COLLECTOR_TOKEN" \
135
141
  --json
136
142
  ```
137
143
 
138
- The `token status` command is included in CLI `0.3.8`. If a fresh environment
144
+ The `token status` command is included in CLI `0.3.9`. If a fresh environment
139
145
  still has an older npm tag, run `usagemax update token status` or
140
146
  `node packages/cli/src/cli.js token status` from this repository until the new
141
147
  package is published.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usagemax",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "Link local coding-agent usage to your UsageMax profile",
5
5
  "keywords": [
6
6
  "usagemax",
package/src/cli.js CHANGED
@@ -18,7 +18,7 @@ import { intervalMinutes, manageService, runScheduledSync } from "./service.js";
18
18
  import { collectorStatusView, requestCollectorStatus, requestSnapshot } from "./transport.js";
19
19
  import { resumeUpload, restartExpiredUpload, withConfigLock } from "./resume.js";
20
20
  import { CCUSAGE_VERSION, ccusageEnvironment, ccusageHome, discoverProviderArchives, SOURCE_INVENTORY_VERSION, sourceInventory, SUPPORTED_SOURCES } from "./sources.js";
21
- import { checkForUpdate, runLatest } from "./updates.js";
21
+ import { checkForUpdate, packageManagerFor, runLatest, updateGlobal } from "./updates.js";
22
22
 
23
23
  // Make the short-lived collector recognizable in Activity Monitor and `ps`.
24
24
  // Windows may still display the underlying node.exe image name in Task Manager.
@@ -26,7 +26,7 @@ process.title = "UsageMax";
26
26
 
27
27
  const require = createRequire(import.meta.url);
28
28
  const executeFile = promisify(execFile);
29
- const VERSION = "0.3.8";
29
+ const VERSION = "0.3.9";
30
30
  const PUBLIC_API_ORIGIN = "https://usagemax.com/api";
31
31
  const DEFAULT_LINK_ENDPOINT = `${PUBLIC_API_ORIGIN}/v1/devices/link`;
32
32
  const DEFAULT_STATUS_ENDPOINT = `${PUBLIC_API_ORIGIN}/v1/devices/status`;
@@ -132,8 +132,9 @@ function help() {
132
132
  process.stdout.write(" usagemax service status|run|uninstall\n");
133
133
  process.stdout.write(" usagemax doctor [--deep] [--json] [--quiet|--no-progress]\n");
134
134
  process.stdout.write(" Check source coverage; --deep parses full history\n");
135
- process.stdout.write(" usagemax update [command args] Check npm and optionally run the latest CLI\n");
136
- process.stdout.write(" [--json] Emit machine-readable update status\n");
135
+ process.stdout.write(" usagemax update Update the global CLI through Bun or npm\n");
136
+ process.stdout.write(" [--check] [--json] Check only or emit machine-readable status\n");
137
+ process.stdout.write(" usagemax update <command args> Run a command through the latest CLI\n");
137
138
  process.stdout.write(" usagemax report [...args] Run a local ccusage report\n");
138
139
  process.stdout.write(" usagemax unlink [--revoke] Remove locally; --revoke also disables uploads\n");
139
140
  process.stdout.write(" [--json] Emit machine-readable unlink status\n");
@@ -622,6 +623,9 @@ async function report(args) {
622
623
 
623
624
  async function update(args = []) {
624
625
  const json = args.includes("--json");
626
+ const checkOnly = args.includes("--check") || args.includes("--no-install");
627
+ const commandArgs = args.filter((arg) => !["--check", "--no-install", "--json"].includes(arg));
628
+ const handoffArgs = commandArgs.length ? [...commandArgs, ...(json ? ["--json"] : [])] : [];
625
629
  const check = await checkForUpdate(configDirectory(), VERSION, { force: true });
626
630
  if (!check.latest) {
627
631
  const result = { version: VERSION, latest: null, newer: false, status: "unavailable" };
@@ -633,15 +637,31 @@ async function update(args = []) {
633
637
  process.stdout.write(json ? `${JSON.stringify(result)}\n` : `UsageMax CLI ${VERSION} is current.\n`);
634
638
  return;
635
639
  }
636
- const result = { version: VERSION, latest: check.latest, newer: true, status: "available", command: "usagemax update sync" };
637
- process.stdout.write(json ? `${JSON.stringify(result)}\n` : `UsageMax CLI ${check.latest} is available (current ${VERSION}).\n`);
638
- const commandArgs = args.filter((arg) => arg !== "--check");
639
- if (!commandArgs.length || args.includes("--check")) {
640
- if (!json) process.stdout.write("Run `usagemax update sync` to hand off the next command to the latest npm release.\n");
640
+ if (handoffArgs.length) {
641
+ const result = { version: VERSION, latest: check.latest, newer: true, status: "available", command: `usagemax update ${commandArgs.join(" ")}` };
642
+ process.stdout.write(json ? `${JSON.stringify(result)}\n` : `UsageMax CLI ${check.latest} is available (current ${VERSION}).\n`);
643
+ if (!json) process.stdout.write(`Launching UsageMax ${check.latest}…\n`);
644
+ return runLatest(handoffArgs);
645
+ }
646
+ const manager = packageManagerFor({ cliPath: process.argv[1], runtime: process.versions, env: process.env });
647
+ const installCommand = manager === "bun" ? "bun add --global usagemax@latest" : "npm install --global usagemax@latest";
648
+ const available = { version: VERSION, latest: check.latest, newer: true, status: "available", manager, installCommand };
649
+ if (checkOnly) {
650
+ process.stdout.write(json ? `${JSON.stringify(available)}\n` : `UsageMax CLI ${check.latest} is available (current ${VERSION}). Run \`${installCommand}\`.\n`);
641
651
  return;
642
652
  }
643
- if (!json) process.stdout.write(`Launching UsageMax ${check.latest}…\n`);
644
- return runLatest(commandArgs);
653
+ if (!json) process.stdout.write(`Updating UsageMax ${VERSION} → ${check.latest} via ${manager}…\n`);
654
+ try {
655
+ await updateGlobal({ latest: check.latest, manager, quiet: json });
656
+ } catch (error) {
657
+ const result = { ...available, status: "failed", error: error instanceof Error ? error.message : String(error) };
658
+ if (json) process.stdout.write(`${JSON.stringify(result)}\n`);
659
+ else process.stderr.write(`UsageMax update failed: ${result.error}\n`);
660
+ process.exitCode = 1;
661
+ return;
662
+ }
663
+ const result = { ...available, status: "updated", installedVersion: check.latest };
664
+ process.stdout.write(json ? `${JSON.stringify(result)}\n` : `UsageMax CLI is now ${check.latest}.\n`);
645
665
  }
646
666
 
647
667
  async function maybeUpdate(command, args) {
package/src/updates.js CHANGED
@@ -83,6 +83,43 @@ function runner() {
83
83
  return { command: "bunx", prefix: ["--bun"] };
84
84
  }
85
85
 
86
+ /**
87
+ * Pick the package manager that owns the current invocation. Bun's global and
88
+ * ephemeral package paths are explicit; npm remains the safe default for a
89
+ * normal Node installation. The override is useful for managed environments
90
+ * where the executable is wrapped by a fleet-specific package manager.
91
+ */
92
+ export function packageManagerFor({ cliPath = process.argv[1], runtime = process.versions, env = process.env } = {}) {
93
+ const override = String(env.USAGEMAX_PACKAGE_MANAGER || "").trim().toLowerCase();
94
+ if (override === "bun" || override === "npm") return override;
95
+ const normalizedPath = String(cliPath || "").replaceAll("\\", "/");
96
+ if (normalizedPath.includes("/.bun/install/") || runtime?.bun) return "bun";
97
+ if (env.npm_execpath || String(env.npm_config_user_agent || "").startsWith("npm/")) return "npm";
98
+ return "npm";
99
+ }
100
+
101
+ /** Install the current public release into the user's global package scope. */
102
+ export async function updateGlobal({ latest = "latest", manager = packageManagerFor(), spawnImpl = spawn, env = process.env, quiet = false, platform = process.platform } = {}) {
103
+ if (manager !== "bun" && manager !== "npm") throw new Error(`Unsupported package manager: ${manager}`);
104
+ if (!/^\d+\.\d+\.\d+$/.test(String(latest)) && latest !== "latest") throw new Error("Invalid UsageMax release version.");
105
+ const command = manager === "bun" ? (platform === "win32" ? "bun.exe" : "bun") : (platform === "win32" ? "npm.cmd" : "npm");
106
+ const args = manager === "bun"
107
+ ? ["add", "--global", `usagemax@${latest}`]
108
+ : ["install", "--global", `usagemax@${latest}`];
109
+ const child = spawnImpl(command, args, {
110
+ stdio: quiet ? ["ignore", "pipe", "pipe"] : "inherit",
111
+ env: { ...env, NPM_CONFIG_UPDATE_NOTIFIER: "false" },
112
+ });
113
+ return new Promise((resolve, reject) => {
114
+ child.once("error", reject);
115
+ child.once("exit", (status) => {
116
+ const code = status ?? 1;
117
+ if (code === 0) resolve({ manager, command, args });
118
+ else reject(Object.assign(new Error(`${command} ${args.join(" ")} exited with code ${code}.`), { code: "USAGEMAX_UPDATE_FAILED", exitCode: code }));
119
+ });
120
+ });
121
+ }
122
+
86
123
  /** Re-run a command through the current npm dist-tag without requiring @latest. */
87
124
  export async function runLatest(args, { spawnImpl = spawn } = {}) {
88
125
  const selected = runner();