sidekick-shared 0.13.6 → 0.13.8

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
@@ -77,10 +77,22 @@ import { fetchQuota, readClaudeMaxCredentials } from 'sidekick-shared';
77
77
  const creds = readClaudeMaxCredentials();
78
78
  if (creds) {
79
79
  const quota = await fetchQuota(creds.accessToken);
80
- console.log(`5-hour utilization: ${quota.fiveHour.percentUsed}%`);
80
+ if (quota.available) {
81
+ console.log(`5-hour utilization: ${quota.fiveHour.utilization}%`);
82
+ } else {
83
+ console.log(quota.failureKind, quota.httpStatus, quota.retryAfterMs);
84
+ }
81
85
  }
82
86
  ```
83
87
 
88
+ Unavailable quota responses remain non-throwing and may include:
89
+
90
+ - `failureKind`: `auth | network | rate_limit | server | unknown`
91
+ - `httpStatus`: HTTP response status when available
92
+ - `retryAfterMs`: retry delay in milliseconds for `429` responses when the API provides `Retry-After`
93
+
94
+ For first-party style messaging, `describeQuotaFailure()` maps unavailable quota states to stable alert keys plus display-ready severity/title/message/detail fields for CLI and VS Code consumers.
95
+
84
96
  ## Building
85
97
 
86
98
  ```bash
package/dist/index.d.ts CHANGED
@@ -83,5 +83,7 @@ export { readClaudeMaxCredentials, readClaudeMaxAccessTokenSync } from './creden
83
83
  export type { ClaudeMaxCredentials } from './credentials';
84
84
  export { fetchQuota } from './quota';
85
85
  export type { QuotaWindow, QuotaState } from './quota';
86
+ export { describeQuotaFailure } from './quotaPresentation';
87
+ export type { QuotaFailureDescriptor } from './quotaPresentation';
86
88
  export { fetchProviderStatus } from './providerStatus';
87
89
  export type { ProviderStatusState } from './providerStatus';
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.findActiveClaudeSession = exports.discoverSessionDirectory = exports.getClaudeSessionDirectory = exports.encodeClaudeWorkspacePath = exports.detectSessionActivity = exports.extractTaskInfo = exports.scanSubagentDir = exports.normalizeCodexToolInput = exports.normalizeCodexToolName = exports.extractPatchFilePaths = exports.CodexRolloutParser = exports.parseDbPartData = exports.parseDbMessageData = exports.convertOpenCodeMessage = exports.detectPlanModeFromText = exports.normalizeToolInput = exports.normalizeToolName = exports.TRUNCATION_PATTERNS = exports.JsonlParser = exports.CodexProvider = exports.OpenCodeProvider = exports.ClaudeCodeProvider = exports.getAllDetectedProviders = exports.detectProvider = exports.readClaudeCodePlanFiles = exports.getPlanAnalytics = exports.writePlans = exports.getLatestPlan = exports.readPlans = exports.readLatestHandoff = exports.readHistory = exports.readNotes = exports.readDecisions = exports.readTasks = exports.getProjectSlugRaw = exports.getProjectSlug = exports.encodeWorkspacePath = exports.getGlobalDataPath = exports.getProjectDataPath = exports.getConfigDir = exports.MAX_PLANS_PER_PROJECT = exports.PLAN_SCHEMA_VERSION = exports.createEmptyTokenTotals = exports.HISTORICAL_DATA_SCHEMA_VERSION = exports.STALENESS_THRESHOLDS = exports.IMPORTANCE_DECAY_FACTORS = exports.KNOWLEDGE_NOTE_SCHEMA_VERSION = exports.DECISION_LOG_SCHEMA_VERSION = exports.normalizeTaskStatus = exports.TASK_PERSISTENCE_SCHEMA_VERSION = void 0;
7
7
  exports.HeatmapTracker = exports.FrequencyTracker = exports.getSnapshotPath = exports.isSnapshotValid = exports.deleteSnapshot = exports.loadSnapshot = exports.saveSnapshot = exports.parseTodoDependencies = exports.EventAggregator = exports.getRandomPhrase = exports.ALL_PHRASES = exports.HIGHLIGHT_CSS = exports.clearHighlightCache = exports.highlightEvent = exports.formatSessionJson = exports.formatSessionMarkdown = exports.formatSessionText = exports.classifyNoise = exports.shouldMergeWithPrevious = exports.classifyFollowEvent = exports.classifyMessage = exports.getSoftNoiseReason = exports.isHardNoiseFollowEvent = exports.isHardNoise = exports.formatToolSummary = exports.toFollowEvents = exports.createWatcher = exports.parseChangelog = exports.extractProposedPlanShared = exports.parsePlanMarkdownShared = exports.PlanExtractor = exports.composeContext = exports.FilterEngine = exports.searchSessions = exports.CodexDatabase = exports.OpenCodeDatabase = exports.discoverDebugLogs = exports.collapseDuplicates = exports.filterByLevel = exports.parseDebugLog = exports.scanSubagentTraces = exports.findAllSessionsWithWorktrees = exports.discoverWorktreeSiblings = exports.resolveWorktreeMainRepo = exports.getAllClaudeProjectFolders = exports.decodeEncodedPath = exports.getMostRecentlyActiveSessionDir = exports.findSubdirectorySessionDirs = exports.findSessionsInDirectory = exports.findAllClaudeSessions = void 0;
8
- exports.fetchProviderStatus = exports.fetchQuota = exports.readClaudeMaxAccessTokenSync = exports.readClaudeMaxCredentials = exports.openInBrowser = exports.parseTranscript = exports.generateHtmlReport = exports.PatternExtractor = void 0;
8
+ exports.fetchProviderStatus = exports.describeQuotaFailure = exports.fetchQuota = exports.readClaudeMaxAccessTokenSync = exports.readClaudeMaxCredentials = exports.openInBrowser = exports.parseTranscript = exports.generateHtmlReport = exports.PatternExtractor = void 0;
9
9
  var taskPersistence_1 = require("./types/taskPersistence");
10
10
  Object.defineProperty(exports, "TASK_PERSISTENCE_SCHEMA_VERSION", { enumerable: true, get: function () { return taskPersistence_1.TASK_PERSISTENCE_SCHEMA_VERSION; } });
11
11
  Object.defineProperty(exports, "normalizeTaskStatus", { enumerable: true, get: function () { return taskPersistence_1.normalizeTaskStatus; } });
@@ -181,6 +181,8 @@ Object.defineProperty(exports, "readClaudeMaxAccessTokenSync", { enumerable: tru
181
181
  // Quota
182
182
  var quota_1 = require("./quota");
183
183
  Object.defineProperty(exports, "fetchQuota", { enumerable: true, get: function () { return quota_1.fetchQuota; } });
184
+ var quotaPresentation_1 = require("./quotaPresentation");
185
+ Object.defineProperty(exports, "describeQuotaFailure", { enumerable: true, get: function () { return quotaPresentation_1.describeQuotaFailure; } });
184
186
  // Provider Status
185
187
  var providerStatus_1 = require("./providerStatus");
186
188
  Object.defineProperty(exports, "fetchProviderStatus", { enumerable: true, get: function () { return providerStatus_1.fetchProviderStatus; } });
package/dist/quota.d.ts CHANGED
@@ -17,6 +17,12 @@ export interface QuotaState {
17
17
  available: boolean;
18
18
  /** Human-readable error if not available */
19
19
  error?: string;
20
+ /** Machine-readable failure classification for unavailable states */
21
+ failureKind?: 'auth' | 'network' | 'rate_limit' | 'server' | 'unknown';
22
+ /** HTTP status code for unavailable API responses */
23
+ httpStatus?: number;
24
+ /** Retry delay in milliseconds when rate-limited */
25
+ retryAfterMs?: number;
20
26
  /** Projected 5-hour utilization at reset (percentage, capped at 200) */
21
27
  projectedFiveHour?: number;
22
28
  /** Projected 7-day utilization at reset (percentage, capped at 200) */
package/dist/quota.js CHANGED
@@ -29,14 +29,27 @@ function projectFromElapsed(utilization, resetsAt, windowMs) {
29
29
  /**
30
30
  * Helper to build an unavailable QuotaState.
31
31
  */
32
- function unavailableState(error) {
32
+ function unavailableState(error, meta = {}) {
33
33
  return {
34
34
  fiveHour: { utilization: 0, resetsAt: '' },
35
35
  sevenDay: { utilization: 0, resetsAt: '' },
36
36
  available: false,
37
37
  error,
38
+ ...meta,
38
39
  };
39
40
  }
41
+ function parseRetryAfterMs(retryAfter) {
42
+ if (!retryAfter)
43
+ return undefined;
44
+ const seconds = Number(retryAfter);
45
+ if (Number.isFinite(seconds) && seconds >= 0) {
46
+ return Math.round(seconds * 1000);
47
+ }
48
+ const retryAt = Date.parse(retryAfter);
49
+ if (Number.isNaN(retryAt))
50
+ return undefined;
51
+ return Math.max(retryAt - Date.now(), 0);
52
+ }
40
53
  /**
41
54
  * Fetch current quota utilization from the Anthropic OAuth usage API.
42
55
  *
@@ -57,9 +70,29 @@ async function fetchQuota(accessToken) {
57
70
  },
58
71
  });
59
72
  if (!res.ok) {
60
- if (res.status === 401)
61
- return unavailableState('Sign in to Claude Code to view quota');
62
- return unavailableState(`API error: ${res.status}`);
73
+ if (res.status === 401) {
74
+ return unavailableState('Sign in to Claude Code to view quota', {
75
+ failureKind: 'auth',
76
+ httpStatus: res.status,
77
+ });
78
+ }
79
+ if (res.status === 429) {
80
+ return unavailableState(`API error: ${res.status}`, {
81
+ failureKind: 'rate_limit',
82
+ httpStatus: res.status,
83
+ retryAfterMs: parseRetryAfterMs(res.headers.get('retry-after')),
84
+ });
85
+ }
86
+ if (res.status >= 500 && res.status <= 599) {
87
+ return unavailableState(`API error: ${res.status}`, {
88
+ failureKind: 'server',
89
+ httpStatus: res.status,
90
+ });
91
+ }
92
+ return unavailableState(`API error: ${res.status}`, {
93
+ failureKind: 'unknown',
94
+ httpStatus: res.status,
95
+ });
63
96
  }
64
97
  const data = await res.json();
65
98
  const fiveUtil = data.five_hour?.utilization ?? 0;
@@ -75,6 +108,6 @@ async function fetchQuota(accessToken) {
75
108
  };
76
109
  }
77
110
  catch {
78
- return unavailableState('Network error');
111
+ return unavailableState('Network error', { failureKind: 'network' });
79
112
  }
80
113
  }
@@ -0,0 +1,10 @@
1
+ import type { QuotaState } from './quota';
2
+ export interface QuotaFailureDescriptor {
3
+ severity: 'info' | 'warning' | 'error';
4
+ title: string;
5
+ message: string;
6
+ detail?: string;
7
+ alertKey: string;
8
+ isRetryable: boolean;
9
+ }
10
+ export declare function describeQuotaFailure(quota: QuotaState | null | undefined): QuotaFailureDescriptor | null;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.describeQuotaFailure = describeQuotaFailure;
4
+ function formatRetryAfter(ms) {
5
+ if (ms <= 0)
6
+ return 'now';
7
+ const totalSeconds = Math.ceil(ms / 1000);
8
+ const days = Math.floor(totalSeconds / 86_400);
9
+ const hours = Math.floor((totalSeconds % 86_400) / 3_600);
10
+ const minutes = Math.floor((totalSeconds % 3_600) / 60);
11
+ const seconds = totalSeconds % 60;
12
+ const parts = [];
13
+ if (days > 0)
14
+ parts.push(`${days}d`);
15
+ if (hours > 0)
16
+ parts.push(`${hours}h`);
17
+ if (minutes > 0 && days === 0)
18
+ parts.push(`${minutes}m`);
19
+ if (parts.length === 0)
20
+ parts.push(`${seconds}s`);
21
+ return parts.join(' ');
22
+ }
23
+ function describeQuotaFailure(quota) {
24
+ if (!quota || quota.available || !quota.failureKind)
25
+ return null;
26
+ switch (quota.failureKind) {
27
+ case 'auth':
28
+ if (quota.error === 'No OAuth token available') {
29
+ return {
30
+ severity: 'error',
31
+ title: 'Sign in required',
32
+ message: 'No Claude Code credentials are available in this environment.',
33
+ detail: 'Run `claude` to sign in, then retry quota refresh.',
34
+ alertKey: 'auth:no-credentials',
35
+ isRetryable: false,
36
+ };
37
+ }
38
+ return {
39
+ severity: 'error',
40
+ title: 'Claude Code sign-in expired',
41
+ message: 'The current Claude Code OAuth session was rejected.',
42
+ detail: 'Sign in again to refresh subscription quota.',
43
+ alertKey: `auth:${quota.httpStatus ?? 'unknown'}`,
44
+ isRetryable: false,
45
+ };
46
+ case 'network':
47
+ return {
48
+ severity: 'warning',
49
+ title: 'Quota API unreachable',
50
+ message: 'Could not reach Anthropic from the current environment.',
51
+ detail: 'Check connectivity, proxy, or firewall settings, then retry.',
52
+ alertKey: 'network',
53
+ isRetryable: true,
54
+ };
55
+ case 'rate_limit':
56
+ return {
57
+ severity: 'warning',
58
+ title: 'Quota API rate limited',
59
+ message: quota.retryAfterMs != null
60
+ ? `Retry in ${formatRetryAfter(quota.retryAfterMs)}.`
61
+ : 'Retry shortly.',
62
+ detail: quota.httpStatus != null ? `Anthropic returned HTTP ${quota.httpStatus}.` : undefined,
63
+ alertKey: `rate_limit:${quota.httpStatus ?? 429}`,
64
+ isRetryable: true,
65
+ };
66
+ case 'server':
67
+ return {
68
+ severity: 'warning',
69
+ title: 'Quota API unavailable',
70
+ message: quota.httpStatus != null
71
+ ? `Anthropic returned HTTP ${quota.httpStatus}. Try again shortly.`
72
+ : 'Anthropic quota data is temporarily unavailable.',
73
+ alertKey: `server:${quota.httpStatus ?? 'unknown'}`,
74
+ isRetryable: true,
75
+ };
76
+ case 'unknown':
77
+ return {
78
+ severity: 'error',
79
+ title: 'Unexpected quota response',
80
+ message: quota.httpStatus != null
81
+ ? `Anthropic returned HTTP ${quota.httpStatus}.`
82
+ : (quota.error ?? 'Quota data could not be retrieved.'),
83
+ detail: 'This failure is not classified as retryable.',
84
+ alertKey: `unknown:${quota.httpStatus ?? 'none'}`,
85
+ isRetryable: false,
86
+ };
87
+ }
88
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sidekick-shared",
3
- "version": "0.13.6",
3
+ "version": "0.13.8",
4
4
  "description": "Shared data access layer for Sidekick — readers, types, providers, credentials, quota",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",