@event4u/agent-switch 1.7.0 → 2.1.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/updates.js CHANGED
@@ -47,8 +47,10 @@ export function isNewer(latest, current) {
47
47
  return compareVersions(latest, current) > 0;
48
48
  }
49
49
  /** Reshape a raw GitHub `releases/latest` payload into {@link ReleaseInfo}, or
50
- * null for a draft/prerelease or a payload without a tag. Pure — no network. */
51
- export function parseRelease(raw) {
50
+ * null for a draft/prerelease or a payload without a tag. Pure — no network.
51
+ * `repo` only supplies the fallback URL for a payload missing `html_url`; it
52
+ * defaults to this app's own repo so existing callers are unaffected. */
53
+ export function parseRelease(raw, repo = UPDATE_REPO) {
52
54
  if (!raw || typeof raw !== "object")
53
55
  return null;
54
56
  const r = raw;
@@ -60,11 +62,43 @@ export function parseRelease(raw) {
60
62
  return {
61
63
  tag,
62
64
  name: typeof r.name === "string" && r.name ? r.name : tag,
63
- url: typeof r.html_url === "string" ? r.html_url : `https://github.com/${UPDATE_REPO}/releases`,
65
+ url: typeof r.html_url === "string" ? r.html_url : `https://github.com/${repo}/releases`,
64
66
  notes: typeof r.body === "string" ? r.body : "",
65
67
  publishedAt: typeof r.published_at === "string" ? r.published_at : "",
66
68
  };
67
69
  }
70
+ /** Reshape a raw GitHub `/releases` array (drafts and prereleases dropped by
71
+ * {@link parseRelease}'s existing rule) and order it newest version first, so
72
+ * the ordering is the version math rather than GitHub's creation order. Pure. */
73
+ export function parseReleases(raw, repo = UPDATE_REPO) {
74
+ if (!Array.isArray(raw))
75
+ return [];
76
+ return raw
77
+ .map((r) => parseRelease(r, repo))
78
+ .filter((r) => r !== null)
79
+ .sort((a, b) => compareVersions(b.tag, a.tag));
80
+ }
81
+ /** The releases strictly newer than `current`, newest first — i.e. everything
82
+ * the user has not seen yet, which is what "what changed" actually means for
83
+ * someone several versions behind. Pure. */
84
+ export function releasesNewerThan(releases, current) {
85
+ return releases.filter((r) => isNewer(r.tag, current)).sort((a, b) => compareVersions(b.tag, a.tag));
86
+ }
87
+ /** Recent releases for a repo, newest first. `[]` when the repo has none (404)
88
+ * — a normal empty state. Throws on any other network/HTTP failure so the
89
+ * caller can fall back to {@link fetchLatestRelease} rather than silently
90
+ * reporting "nothing new". One request; the unauthenticated endpoint is
91
+ * rate-limited (60/h per IP), ample for an hourly check. */
92
+ export async function fetchReleases(repo = UPDATE_REPO, perPage = 20) {
93
+ const res = await fetch(`https://api.github.com/repos/${repo}/releases?per_page=${perPage}`, {
94
+ headers: { Accept: "application/vnd.github+json", "User-Agent": `${PACKAGE_NAME} cli-updater` },
95
+ });
96
+ if (res.status === 404)
97
+ return [];
98
+ if (!res.ok)
99
+ throw new Error(`GitHub API returned ${res.status}`);
100
+ return parseReleases(await res.json(), repo);
101
+ }
68
102
  /** The running CLI's version, from the package's own package.json (one level up
69
103
  * from the compiled dist/). "0.0.0" if unreadable so callers never crash. */
70
104
  export function currentVersion() {
package/dist/usage.js CHANGED
@@ -151,13 +151,14 @@ export function maxUtilization(s) {
151
151
  return vals.length ? Math.max(...vals) : null;
152
152
  }
153
153
  /**
154
- * Opt-in auto-switch decision (pure, no I/O). Given the active profile and the
155
- * same-provider candidates with their usage, return the profile to switch to,
156
- * or null. Switches only when the active profile has hit `threshold`% on some
157
- * window AND another candidate has strictly more headroom (lower max
158
- * utilization) while itself staying below `threshold`. Ties keep the first
159
- * candidate. This is a single switch decision gated behind config — never a
160
- * display ranking.
154
+ * Opt-in switch SUGGESTION (pure, no I/O). Given the active profile and the
155
+ * same-provider candidates with their usage, return the profile to suggest
156
+ * switching to, or null. Suggests only when the active profile has hit
157
+ * `threshold`% on some window AND another candidate has strictly more headroom
158
+ * (lower max utilization) while itself staying below `threshold`. Ties keep the
159
+ * first candidate. This computes the suggestion the daemon notifies with and the
160
+ * user-clicked switch modal pre-selects — it never performs the switch itself,
161
+ * and is never a display ranking.
161
162
  */
162
163
  export function pickSwitchTarget(active, candidates, threshold, isEligible = () => true) {
163
164
  const activeSnap = candidates.find((c) => c.name === active)?.snapshot;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@event4u/agent-switch",
3
- "version": "1.7.0",
3
+ "version": "2.1.0",
4
4
  "description": "Switch between multiple Claude Code accounts on macOS, Linux, and Windows via isolated CLAUDE_CONFIG_DIR profiles",
5
5
  "repository": {
6
6
  "type": "git",