@rikcodes/teamclaude 1.1.14-rik.1 → 1.1.14-rik.2
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 +3 -2
- package/package.json +1 -1
- package/src/oauth.js +56 -14
package/README.md
CHANGED
|
@@ -57,8 +57,9 @@ teamclaude run # in another terminal: Claude Code through the proxy
|
|
|
57
57
|
Already logged into Claude Code? `teamclaude import` takes its credentials instead of a fresh OAuth round. API keys, and one email holding accounts in several orgs, are covered in [docs/accounts.md](docs/accounts.md).
|
|
58
58
|
|
|
59
59
|
`teamclaude run` does not require a separate Claude Code login for normal API
|
|
60
|
-
requests. When local Claude OAuth
|
|
61
|
-
mode supplies a local-only bootstrap
|
|
60
|
+
requests. When there is no usable local Claude OAuth (no login, or one that can
|
|
61
|
+
no longer be refreshed), proxy credential mode supplies a local-only bootstrap
|
|
62
|
+
key; the proxy replaces that placeholder
|
|
62
63
|
with the selected TeamClaude account credential before forwarding.
|
|
63
64
|
|
|
64
65
|
## What it does
|
package/package.json
CHANGED
package/src/oauth.js
CHANGED
|
@@ -67,22 +67,48 @@ export async function readKeychainCredentials({ exec = execFileAsync, username =
|
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
69
|
* Import OAuth credentials from a Claude Code credentials file.
|
|
70
|
-
*
|
|
71
|
-
*
|
|
70
|
+
*
|
|
71
|
+
* On macOS Claude Code keeps its live login in the Keychain, and
|
|
72
|
+
* ~/.claude/.credentials.json — when it exists at all — is a snapshot from an
|
|
73
|
+
* earlier login that Claude Code never refreshes. So for the default path on
|
|
74
|
+
* darwin the Keychain is asked first, and the file is only the fallback for a
|
|
75
|
+
* Keychain that carries no token or cannot be read. Reading the file first made
|
|
76
|
+
* a days-old snapshot look like an expired login while Claude Code itself was
|
|
77
|
+
* still signed in. Any other path is a plain file read.
|
|
72
78
|
*/
|
|
73
79
|
export async function importCredentials(filePath, {
|
|
74
80
|
home = homedir(), platform = process.platform, readKeychain = readKeychainCredentials } = {}) {
|
|
75
81
|
const resolvedPath = filePath.replace(/^~/, home);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
const isDefaultPath = resolvedPath === DEFAULT_CREDENTIALS_PATH.replace(/^~/, home);
|
|
83
|
+
const useKeychain = platform === 'darwin' && isDefaultPath;
|
|
84
|
+
|
|
85
|
+
let raw = null;
|
|
86
|
+
let keychainBlank = null; // a Keychain payload that parsed but carried no token
|
|
87
|
+
let keychainErr = null;
|
|
88
|
+
if (useKeychain) {
|
|
89
|
+
try {
|
|
90
|
+
const payload = await readKeychain();
|
|
91
|
+
if (hasToken(payload)) raw = payload;
|
|
92
|
+
else keychainBlank = payload;
|
|
93
|
+
} catch (err) {
|
|
94
|
+
keychainErr = err;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!raw) {
|
|
82
99
|
try {
|
|
83
|
-
raw = await
|
|
84
|
-
} catch (
|
|
85
|
-
|
|
100
|
+
raw = JSON.parse(await readFile(resolvedPath, 'utf-8'));
|
|
101
|
+
} catch (err) {
|
|
102
|
+
if (!useKeychain || err.code !== 'ENOENT') throw err;
|
|
103
|
+
// No file either. A token-less Keychain payload still goes back to the
|
|
104
|
+
// caller, whose own "no credentials" reporting stays in charge; only a
|
|
105
|
+
// Keychain that could not be read at all is an error here.
|
|
106
|
+
if (keychainBlank) {
|
|
107
|
+
raw = keychainBlank;
|
|
108
|
+
} else {
|
|
109
|
+
const detail = keychainErr ? keychainErr.message : 'no item carried a token';
|
|
110
|
+
throw new Error(`${err.message}; macOS Keychain lookup for "${KEYCHAIN_SERVICE}" also failed: ${detail}`);
|
|
111
|
+
}
|
|
86
112
|
}
|
|
87
113
|
}
|
|
88
114
|
|
|
@@ -92,6 +118,9 @@ export async function importCredentials(filePath, {
|
|
|
92
118
|
accessToken: data.accessToken,
|
|
93
119
|
refreshToken: data.refreshToken,
|
|
94
120
|
expiresAt: data.expiresAt,
|
|
121
|
+
// Only Claude Code's own store carries this; leave the key out rather than
|
|
122
|
+
// put an undefined one on every imported account.
|
|
123
|
+
...(data.refreshTokenExpiresAt != null && { refreshTokenExpiresAt: data.refreshTokenExpiresAt }),
|
|
95
124
|
subscriptionType: data.subscriptionType,
|
|
96
125
|
rateLimitTier: data.rateLimitTier,
|
|
97
126
|
};
|
|
@@ -204,10 +233,23 @@ export function isTokenExpired(expiresAt) {
|
|
|
204
233
|
return Date.now() >= normalizeExpiresAt(expiresAt);
|
|
205
234
|
}
|
|
206
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Whether Claude Code has to start in proxy credential mode (on the bootstrap
|
|
238
|
+
* key) because it has no usable OAuth of its own.
|
|
239
|
+
*
|
|
240
|
+
* "Usable" is deliberately loose. An access token that has already expired is
|
|
241
|
+
* fine as long as a refresh token is there: Claude Code refreshes the pair
|
|
242
|
+
* itself at startup, and the proxy relays that refresh untouched. Counting an
|
|
243
|
+
* expired access token as "no OAuth" put Claude Code into API-key mode — which
|
|
244
|
+
* drops subscription mode and disables Claude in Chrome — whenever the
|
|
245
|
+
* credentials it read were a stale snapshot. Only a missing, or itself
|
|
246
|
+
* expired, refresh token means Claude Code cannot sign in on its own.
|
|
247
|
+
*/
|
|
207
248
|
export function needsProxyClientCredential(credentials, now = Date.now()) {
|
|
208
|
-
if (!credentials
|
|
209
|
-
|
|
210
|
-
|
|
249
|
+
if (!credentials) return true;
|
|
250
|
+
const { accessToken, expiresAt, refreshToken, refreshTokenExpiresAt } = credentials;
|
|
251
|
+
const live = (token, expiry) => Boolean(token) && (!expiry || normalizeExpiresAt(expiry) > now);
|
|
252
|
+
return !live(accessToken, expiresAt) && !live(refreshToken, refreshTokenExpiresAt);
|
|
211
253
|
}
|
|
212
254
|
|
|
213
255
|
/**
|