acp-extension-claude 0.13.3 → 0.13.4
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/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +3 -2
- package/dist/lib.d.ts +1 -0
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +1 -0
- package/dist/usage.d.ts +39 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +369 -0
- package/package.json +2 -2
package/dist/acp-agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"acp-agent.d.ts","sourceRoot":"","sources":["../src/acp-agent.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,mBAAmB,EACnB,mBAAmB,EAEnB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAElB,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EAEpB,oBAAoB,EACpB,qBAAqB,EAErB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,sBAAsB,EACtB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EACL,UAAU,EAEV,OAAO,EACP,cAAc,EACd,KAAK,EAEL,0BAA0B,EAC1B,cAAc,EACf,MAAM,gCAAgC,CAAC;AAIxC,OAAO,EAAwC,QAAQ,EAAe,MAAM,YAAY,CAAC;AAYzF,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAKlG,eAAO,MAAM,iBAAiB,QAA2D,CAAC;AAE1F;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAC9B,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;CACjC;AAED,KAAK,OAAO,GAAG;IACb,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;IAChC,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,cAAc,CAAC;IAC/B,eAAe,EAAE,eAAe,CAAC;CAClC,CAAC;AAEF,KAAK,kBAAkB,GACnB;IACA,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,SAAS,CAAC;IAClB,UAAU,EAAE,sBAAsB,GAAG,IAAI,CAAC;CAC3C,GACC;IACA,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;IACrD,aAAa,EAAE,sBAAsB,CAAC;CACvC,CAAC;AAEJ;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,CAAC,EAAE;QACX;;;;;;;;;;;;WAYG;QACH,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;CACH,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,CAAC,EAAE;QAEX,QAAQ,EAAE,MAAM,CAAC;QAEjB,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,CAAC;CACH,CAAC;AAEF,KAAK,YAAY,GAAG;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG;QACb,IAAI,EAAE,UAAU,GAAG,iBAAiB,GAAG,cAAc,CAAC;QACtD,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,GAAG,CAAC;KACZ,CAAC;CACH,CAAC;AAMF,qBAAa,cAAe,YAAW,KAAK;IAC1C,QAAQ,EAAE;QACR,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,MAAM,EAAE,mBAAmB,CAAC;IAC5B,YAAY,EAAE,YAAY,CAAC;IAC3B,mBAAmB,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAAA;KAAE,CAAM;IAChE,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,MAAM,EAAE,MAAM,CAAC;gBAEH,MAAM,EAAE,mBAAmB,EAAE,MAAM,CAAC,EAAE,MAAM;IAOlD,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAgDnE,UAAU,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAclE,oBAAoB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAc9E,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAepF,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzD,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"acp-agent.d.ts","sourceRoot":"","sources":["../src/acp-agent.ts"],"names":[],"mappings":"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"}
|
package/dist/acp-agent.js
CHANGED
|
@@ -150,7 +150,8 @@ export class ClaudeAcpAgent {
|
|
|
150
150
|
cacheReadInputTokens: usage.cacheReadInputTokens,
|
|
151
151
|
cacheCreationInputTokens: usage.cacheCreationInputTokens,
|
|
152
152
|
webSearchRequests: usage.webSearchRequests,
|
|
153
|
-
costUSD: usage.costUSD
|
|
153
|
+
costUSD: usage.costUSD,
|
|
154
|
+
contextWindow: usage.contextWindow
|
|
154
155
|
};
|
|
155
156
|
}
|
|
156
157
|
const usages = {
|
|
@@ -158,7 +159,7 @@ export class ClaudeAcpAgent {
|
|
|
158
159
|
inputTokens: message.usage.input_tokens,
|
|
159
160
|
outputTokens: message.usage.output_tokens,
|
|
160
161
|
cacheCreationInputTokens: message.usage.cache_creation_input_tokens,
|
|
161
|
-
cacheReadInputTokens: message.usage.cache_read_input_tokens
|
|
162
|
+
cacheReadInputTokens: message.usage.cache_read_input_tokens
|
|
162
163
|
},
|
|
163
164
|
modelUsage,
|
|
164
165
|
};
|
package/dist/lib.d.ts
CHANGED
|
@@ -3,5 +3,6 @@ export { loadManagedSettings, applyEnvironmentSettings, nodeToWebReadable, nodeT
|
|
|
3
3
|
export { createMcpServer } from "./mcp-server.js";
|
|
4
4
|
export { toolInfoFromToolUse, planEntries, toolUpdateFromToolResult, createPreToolUseHook, acpToolNames as toolNames, } from "./tools.js";
|
|
5
5
|
export { SettingsManager, type ClaudeCodeSettings, type PermissionSettings, type PermissionDecision, type PermissionCheckResult, type SettingsManagerOptions, } from "./settings.js";
|
|
6
|
+
export { getUsage, UsageApiDeps } from './usage.js';
|
|
6
7
|
export type { ClaudePlanEntry } from "./tools.js";
|
|
7
8
|
//# sourceMappingURL=lib.d.ts.map
|
package/dist/lib.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,MAAM,EACN,kBAAkB,EAClB,6BAA6B,EAC7B,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,EACR,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,wBAAwB,EACxB,oBAAoB,EACpB,YAAY,IAAI,SAAS,GAC1B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,MAAM,EACN,kBAAkB,EAClB,6BAA6B,EAC7B,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,EACR,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,wBAAwB,EACxB,oBAAoB,EACpB,YAAY,IAAI,SAAS,GAC1B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAGnD,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/lib.js
CHANGED
|
@@ -4,3 +4,4 @@ export { loadManagedSettings, applyEnvironmentSettings, nodeToWebReadable, nodeT
|
|
|
4
4
|
export { createMcpServer } from "./mcp-server.js";
|
|
5
5
|
export { toolInfoFromToolUse, planEntries, toolUpdateFromToolResult, createPreToolUseHook, acpToolNames as toolNames, } from "./tools.js";
|
|
6
6
|
export { SettingsManager, } from "./settings.js";
|
|
7
|
+
export { getUsage } from './usage.js';
|
package/dist/usage.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface UsageData {
|
|
2
|
+
planName: string | null;
|
|
3
|
+
fiveHour: number | null;
|
|
4
|
+
sevenDay: number | null;
|
|
5
|
+
fiveHourResetAt: Date | null;
|
|
6
|
+
sevenDayResetAt: Date | null;
|
|
7
|
+
apiUnavailable?: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface UsageApiResponse {
|
|
10
|
+
five_hour?: {
|
|
11
|
+
utilization?: number;
|
|
12
|
+
resets_at?: string;
|
|
13
|
+
};
|
|
14
|
+
seven_day?: {
|
|
15
|
+
utilization?: number;
|
|
16
|
+
resets_at?: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export type UsageApiDeps = {
|
|
20
|
+
homeDir: () => string;
|
|
21
|
+
fetchApi: (accessToken: string) => Promise<UsageApiResponse | null>;
|
|
22
|
+
now: () => number;
|
|
23
|
+
readKeychain: (now: number, homeDir: string) => {
|
|
24
|
+
accessToken: string;
|
|
25
|
+
subscriptionType: string;
|
|
26
|
+
} | null;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Get OAuth usage data from Anthropic API.
|
|
30
|
+
* Returns null if user is an API user (no OAuth credentials) or credentials are expired.
|
|
31
|
+
* Returns { apiUnavailable: true, ... } if API call fails (to show warning in HUD).
|
|
32
|
+
*
|
|
33
|
+
* Uses file-based cache since HUD runs as a new process each render (~300ms).
|
|
34
|
+
* Cache TTL: 60s for success, 15s for failures.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getUsage(overrides?: Partial<UsageApiDeps>): Promise<UsageData | null>;
|
|
37
|
+
export declare function clearCache(homeDir?: string): void;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=usage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,SAAS;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;IAC7B,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC5B;AAaD,UAAU,gBAAgB;IACtB,SAAS,CAAC,EAAE;QACR,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,SAAS,CAAC,EAAE;QACR,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACL;AA8DD,MAAM,MAAM,YAAY,GAAG;IACvB,OAAO,EAAE,MAAM,MAAM,CAAC;IACtB,QAAQ,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IACpE,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC5G,CAAC;AASF;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAAC,SAAS,GAAE,OAAO,CAAC,YAAY,CAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAkE/F;AA8PD,wBAAgB,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAWjD"}
|
package/dist/usage.js
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
// https://github.com/jarrodwatts/claude-hud/tree/main
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import * as https from 'https';
|
|
6
|
+
import { execFileSync } from 'child_process';
|
|
7
|
+
const debug = console.debug;
|
|
8
|
+
// File-based cache (HUD runs as new process each render, so in-memory cache won't persist)
|
|
9
|
+
const CACHE_TTL_MS = 60000; // 60 seconds
|
|
10
|
+
const CACHE_FAILURE_TTL_MS = 15000; // 15 seconds for failed requests
|
|
11
|
+
const KEYCHAIN_TIMEOUT_MS = 5000;
|
|
12
|
+
const KEYCHAIN_BACKOFF_MS = 60000; // Backoff on keychain failures to avoid re-prompting
|
|
13
|
+
function getCachePath(homeDir) {
|
|
14
|
+
return path.join(homeDir, '.claude', 'plugins', 'claude-hud', '.usage-cache.json');
|
|
15
|
+
}
|
|
16
|
+
function readCache(homeDir, now) {
|
|
17
|
+
try {
|
|
18
|
+
const cachePath = getCachePath(homeDir);
|
|
19
|
+
if (!fs.existsSync(cachePath))
|
|
20
|
+
return null;
|
|
21
|
+
const content = fs.readFileSync(cachePath, 'utf8');
|
|
22
|
+
const cache = JSON.parse(content);
|
|
23
|
+
// Check TTL - use shorter TTL for failure results
|
|
24
|
+
const ttl = cache.data.apiUnavailable ? CACHE_FAILURE_TTL_MS : CACHE_TTL_MS;
|
|
25
|
+
if (now - cache.timestamp >= ttl)
|
|
26
|
+
return null;
|
|
27
|
+
// JSON.stringify converts Date to ISO string, so we need to reconvert on read.
|
|
28
|
+
// new Date() handles both Date objects and ISO strings safely.
|
|
29
|
+
const data = cache.data;
|
|
30
|
+
if (data.fiveHourResetAt) {
|
|
31
|
+
data.fiveHourResetAt = new Date(data.fiveHourResetAt);
|
|
32
|
+
}
|
|
33
|
+
if (data.sevenDayResetAt) {
|
|
34
|
+
data.sevenDayResetAt = new Date(data.sevenDayResetAt);
|
|
35
|
+
}
|
|
36
|
+
return data;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function writeCache(homeDir, data, timestamp) {
|
|
43
|
+
try {
|
|
44
|
+
const cachePath = getCachePath(homeDir);
|
|
45
|
+
const cacheDir = path.dirname(cachePath);
|
|
46
|
+
if (!fs.existsSync(cacheDir)) {
|
|
47
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
const cache = { data, timestamp };
|
|
50
|
+
fs.writeFileSync(cachePath, JSON.stringify(cache), 'utf8');
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Ignore cache write failures
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const defaultDeps = {
|
|
57
|
+
homeDir: () => os.homedir(),
|
|
58
|
+
fetchApi: fetchUsageApi,
|
|
59
|
+
now: () => Date.now(),
|
|
60
|
+
readKeychain: readKeychainCredentials,
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Get OAuth usage data from Anthropic API.
|
|
64
|
+
* Returns null if user is an API user (no OAuth credentials) or credentials are expired.
|
|
65
|
+
* Returns { apiUnavailable: true, ... } if API call fails (to show warning in HUD).
|
|
66
|
+
*
|
|
67
|
+
* Uses file-based cache since HUD runs as a new process each render (~300ms).
|
|
68
|
+
* Cache TTL: 60s for success, 15s for failures.
|
|
69
|
+
*/
|
|
70
|
+
export async function getUsage(overrides = {}) {
|
|
71
|
+
const deps = { ...defaultDeps, ...overrides };
|
|
72
|
+
const now = deps.now();
|
|
73
|
+
const homeDir = deps.homeDir();
|
|
74
|
+
// Check file-based cache first
|
|
75
|
+
const cached = readCache(homeDir, now);
|
|
76
|
+
if (cached) {
|
|
77
|
+
return cached;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const credentials = readCredentials(homeDir, now, deps.readKeychain);
|
|
81
|
+
if (!credentials) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const { accessToken, subscriptionType } = credentials;
|
|
85
|
+
// Determine plan name from subscriptionType
|
|
86
|
+
const planName = getPlanName(subscriptionType);
|
|
87
|
+
if (!planName) {
|
|
88
|
+
// API user, no usage limits to show
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
// Fetch usage from API
|
|
92
|
+
const apiResponse = await deps.fetchApi(accessToken);
|
|
93
|
+
if (!apiResponse) {
|
|
94
|
+
// API call failed, cache the failure to prevent retry storms
|
|
95
|
+
const failureResult = {
|
|
96
|
+
planName,
|
|
97
|
+
fiveHour: null,
|
|
98
|
+
sevenDay: null,
|
|
99
|
+
fiveHourResetAt: null,
|
|
100
|
+
sevenDayResetAt: null,
|
|
101
|
+
apiUnavailable: true,
|
|
102
|
+
};
|
|
103
|
+
writeCache(homeDir, failureResult, now);
|
|
104
|
+
return failureResult;
|
|
105
|
+
}
|
|
106
|
+
// Parse response - API returns 0-100 percentage directly
|
|
107
|
+
// Clamp to 0-100 and handle NaN/Infinity
|
|
108
|
+
const fiveHour = parseUtilization(apiResponse.five_hour?.utilization);
|
|
109
|
+
const sevenDay = parseUtilization(apiResponse.seven_day?.utilization);
|
|
110
|
+
const fiveHourResetAt = parseDate(apiResponse.five_hour?.resets_at);
|
|
111
|
+
const sevenDayResetAt = parseDate(apiResponse.seven_day?.resets_at);
|
|
112
|
+
const result = {
|
|
113
|
+
planName,
|
|
114
|
+
fiveHour,
|
|
115
|
+
sevenDay,
|
|
116
|
+
fiveHourResetAt,
|
|
117
|
+
sevenDayResetAt,
|
|
118
|
+
};
|
|
119
|
+
// Write to file cache
|
|
120
|
+
writeCache(homeDir, result, now);
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
debug('getUsage failed:', error);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get path for keychain failure backoff cache.
|
|
130
|
+
* Separate from usage cache to track keychain-specific failures.
|
|
131
|
+
*/
|
|
132
|
+
function getKeychainBackoffPath(homeDir) {
|
|
133
|
+
return path.join(homeDir, '.claude', 'plugins', 'claude-hud', '.keychain-backoff');
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Check if we're in keychain backoff period (recent failure/timeout).
|
|
137
|
+
* Prevents re-prompting user on every render cycle.
|
|
138
|
+
*/
|
|
139
|
+
function isKeychainBackoff(homeDir, now) {
|
|
140
|
+
try {
|
|
141
|
+
const backoffPath = getKeychainBackoffPath(homeDir);
|
|
142
|
+
if (!fs.existsSync(backoffPath))
|
|
143
|
+
return false;
|
|
144
|
+
const timestamp = parseInt(fs.readFileSync(backoffPath, 'utf8'), 10);
|
|
145
|
+
return now - timestamp < KEYCHAIN_BACKOFF_MS;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Record keychain failure for backoff.
|
|
153
|
+
*/
|
|
154
|
+
function recordKeychainFailure(homeDir, now) {
|
|
155
|
+
try {
|
|
156
|
+
const backoffPath = getKeychainBackoffPath(homeDir);
|
|
157
|
+
const dir = path.dirname(backoffPath);
|
|
158
|
+
if (!fs.existsSync(dir)) {
|
|
159
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
160
|
+
}
|
|
161
|
+
fs.writeFileSync(backoffPath, String(now), 'utf8');
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Ignore write failures
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Read credentials from macOS Keychain.
|
|
169
|
+
* Claude Code 2.x stores OAuth credentials in the macOS Keychain under "Claude Code-credentials".
|
|
170
|
+
* Returns null if not on macOS or credentials not found.
|
|
171
|
+
*
|
|
172
|
+
* Security: Uses execFileSync with absolute path to avoid shell injection and PATH hijacking.
|
|
173
|
+
*/
|
|
174
|
+
function readKeychainCredentials(now, homeDir) {
|
|
175
|
+
// Only available on macOS
|
|
176
|
+
if (process.platform !== 'darwin') {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
// Check backoff to avoid re-prompting on every render after a failure
|
|
180
|
+
if (isKeychainBackoff(homeDir, now)) {
|
|
181
|
+
debug('Keychain in backoff period, skipping');
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
// Read from macOS Keychain using security command
|
|
186
|
+
// Security: Use execFileSync with absolute path and args array (no shell)
|
|
187
|
+
const keychainData = execFileSync('/usr/bin/security', ['find-generic-password', '-s', 'Claude Code-credentials', '-w'], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: KEYCHAIN_TIMEOUT_MS }).trim();
|
|
188
|
+
if (!keychainData) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
const data = JSON.parse(keychainData);
|
|
192
|
+
return parseCredentialsData(data, now);
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
// Security: Only log error message, not full error object (may contain stdout/stderr with tokens)
|
|
196
|
+
const message = error instanceof Error ? error.message : 'unknown error';
|
|
197
|
+
debug('Failed to read from macOS Keychain:', message);
|
|
198
|
+
// Record failure for backoff to avoid re-prompting
|
|
199
|
+
recordKeychainFailure(homeDir, now);
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Read credentials from file (legacy method).
|
|
205
|
+
* Older versions of Claude Code stored credentials in ~/.claude/.credentials.json
|
|
206
|
+
*/
|
|
207
|
+
function readFileCredentials(homeDir, now) {
|
|
208
|
+
const credentialsPath = path.join(homeDir, '.claude', '.credentials.json');
|
|
209
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const content = fs.readFileSync(credentialsPath, 'utf8');
|
|
214
|
+
const data = JSON.parse(content);
|
|
215
|
+
return parseCredentialsData(data, now);
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
debug('Failed to read credentials file:', error);
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Parse and validate credentials data from either Keychain or file.
|
|
224
|
+
*/
|
|
225
|
+
function parseCredentialsData(data, now) {
|
|
226
|
+
const accessToken = data.claudeAiOauth?.accessToken;
|
|
227
|
+
const subscriptionType = data.claudeAiOauth?.subscriptionType ?? '';
|
|
228
|
+
if (!accessToken) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
// Check if token is expired (expiresAt is Unix ms timestamp)
|
|
232
|
+
// Use != null to handle expiresAt=0 correctly (would be expired)
|
|
233
|
+
const expiresAt = data.claudeAiOauth?.expiresAt;
|
|
234
|
+
if (expiresAt != null && expiresAt <= now) {
|
|
235
|
+
debug('OAuth token expired');
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
return { accessToken, subscriptionType };
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Read OAuth credentials, trying macOS Keychain first (Claude Code 2.x),
|
|
242
|
+
* then falling back to file-based credentials (older versions).
|
|
243
|
+
*
|
|
244
|
+
* Token priority: Keychain token is authoritative (Claude Code 2.x stores current token there).
|
|
245
|
+
* SubscriptionType: Can be supplemented from file if keychain lacks it (display-only field).
|
|
246
|
+
*/
|
|
247
|
+
function readCredentials(homeDir, now, readKeychain) {
|
|
248
|
+
// Try macOS Keychain first (Claude Code 2.x)
|
|
249
|
+
const keychainCreds = readKeychain(now, homeDir);
|
|
250
|
+
if (keychainCreds) {
|
|
251
|
+
if (keychainCreds.subscriptionType) {
|
|
252
|
+
debug('Using credentials from macOS Keychain');
|
|
253
|
+
return keychainCreds;
|
|
254
|
+
}
|
|
255
|
+
// Keychain has token but no subscriptionType - try to supplement from file
|
|
256
|
+
const fileCreds = readFileCredentials(homeDir, now);
|
|
257
|
+
if (fileCreds?.subscriptionType) {
|
|
258
|
+
debug('Using keychain token with file subscriptionType');
|
|
259
|
+
return {
|
|
260
|
+
accessToken: keychainCreds.accessToken,
|
|
261
|
+
subscriptionType: fileCreds.subscriptionType,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
// No subscriptionType available - use keychain token anyway
|
|
265
|
+
debug('Using keychain token without subscriptionType');
|
|
266
|
+
return keychainCreds;
|
|
267
|
+
}
|
|
268
|
+
// Fall back to file-based credentials (older versions or non-macOS)
|
|
269
|
+
const fileCreds = readFileCredentials(homeDir, now);
|
|
270
|
+
if (fileCreds) {
|
|
271
|
+
debug('Using credentials from file');
|
|
272
|
+
return fileCreds;
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
function getPlanName(subscriptionType) {
|
|
277
|
+
const lower = subscriptionType.toLowerCase();
|
|
278
|
+
if (lower.includes('max'))
|
|
279
|
+
return 'Max';
|
|
280
|
+
if (lower.includes('pro'))
|
|
281
|
+
return 'Pro';
|
|
282
|
+
if (lower.includes('team'))
|
|
283
|
+
return 'Team';
|
|
284
|
+
// API users don't have subscriptionType or have 'api'
|
|
285
|
+
if (!subscriptionType || lower.includes('api'))
|
|
286
|
+
return null;
|
|
287
|
+
// Unknown subscription type - show it capitalized
|
|
288
|
+
return subscriptionType.charAt(0).toUpperCase() + subscriptionType.slice(1);
|
|
289
|
+
}
|
|
290
|
+
/** Parse utilization value, clamping to 0-100 and handling NaN/Infinity */
|
|
291
|
+
function parseUtilization(value) {
|
|
292
|
+
if (value == null)
|
|
293
|
+
return null;
|
|
294
|
+
if (!Number.isFinite(value))
|
|
295
|
+
return null; // Handles NaN and Infinity
|
|
296
|
+
return Math.round(Math.max(0, Math.min(100, value)));
|
|
297
|
+
}
|
|
298
|
+
/** Parse ISO date string safely, returning null for invalid dates */
|
|
299
|
+
function parseDate(dateStr) {
|
|
300
|
+
if (!dateStr)
|
|
301
|
+
return null;
|
|
302
|
+
const date = new Date(dateStr);
|
|
303
|
+
// Check for Invalid Date
|
|
304
|
+
if (isNaN(date.getTime())) {
|
|
305
|
+
debug('Invalid date string:', dateStr);
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
return date;
|
|
309
|
+
}
|
|
310
|
+
function fetchUsageApi(accessToken) {
|
|
311
|
+
return new Promise((resolve) => {
|
|
312
|
+
const options = {
|
|
313
|
+
hostname: 'api.anthropic.com',
|
|
314
|
+
path: '/api/oauth/usage',
|
|
315
|
+
method: 'GET',
|
|
316
|
+
headers: {
|
|
317
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
318
|
+
'anthropic-beta': 'oauth-2025-04-20',
|
|
319
|
+
'User-Agent': 'claude-hud/1.0',
|
|
320
|
+
},
|
|
321
|
+
timeout: 5000,
|
|
322
|
+
};
|
|
323
|
+
const req = https.request(options, (res) => {
|
|
324
|
+
let data = '';
|
|
325
|
+
res.on('data', (chunk) => {
|
|
326
|
+
data += chunk.toString();
|
|
327
|
+
});
|
|
328
|
+
res.on('end', () => {
|
|
329
|
+
if (res.statusCode !== 200) {
|
|
330
|
+
debug('API returned non-200 status:', res.statusCode);
|
|
331
|
+
resolve(null);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const parsed = JSON.parse(data);
|
|
336
|
+
resolve(parsed);
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
debug('Failed to parse API response:', error);
|
|
340
|
+
resolve(null);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
req.on('error', (error) => {
|
|
345
|
+
debug('API request error:', error);
|
|
346
|
+
resolve(null);
|
|
347
|
+
});
|
|
348
|
+
req.on('timeout', () => {
|
|
349
|
+
debug('API request timeout');
|
|
350
|
+
req.destroy();
|
|
351
|
+
resolve(null);
|
|
352
|
+
});
|
|
353
|
+
req.end();
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
// Export for testing
|
|
357
|
+
export function clearCache(homeDir) {
|
|
358
|
+
if (homeDir) {
|
|
359
|
+
try {
|
|
360
|
+
const cachePath = getCachePath(homeDir);
|
|
361
|
+
if (fs.existsSync(cachePath)) {
|
|
362
|
+
fs.unlinkSync(cachePath);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
// Ignore
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "acp-extension-claude",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.4",
|
|
4
4
|
"description": "Forked ACP-compatible coding agent with improved timeout handling and logging",
|
|
5
5
|
"main": "dist/lib.js",
|
|
6
6
|
"types": "dist/lib.d.ts",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@modelcontextprotocol/sdk": "1.25.2",
|
|
39
39
|
"diff": "8.0.3",
|
|
40
40
|
"minimatch": "10.1.1",
|
|
41
|
-
"acp-extension-core": "0.0.
|
|
41
|
+
"acp-extension-core": "0.0.4"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@anthropic-ai/sdk": "0.71.2",
|