teamclaude-cloud 1.5.1 → 1.5.3
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/package.json +1 -1
- package/src/cloud.js +6 -3
- package/src/index.js +46 -10
- package/src/tui.js +1 -2
package/package.json
CHANGED
package/src/cloud.js
CHANGED
|
@@ -218,8 +218,11 @@ export function buildUsageFromQuota(entry) {
|
|
|
218
218
|
* `usageByUuid` (optional Map/object accountUuid → usage from buildUsageFromQuota)
|
|
219
219
|
* rides along so the dashboard can show 5h/7d/Fable utilization.
|
|
220
220
|
*/
|
|
221
|
-
export async function cloudPush(cloud, accounts, usageByUuid = null) {
|
|
222
|
-
|
|
221
|
+
export async function cloudPush(cloud, accounts, usageByUuid = null, authKey = null) {
|
|
222
|
+
// Auth: an auth key (push to the key's OWNER tenant, no owner login) OR the owner
|
|
223
|
+
// session. The key path only needs the endpoint (requireCloud), not a session.
|
|
224
|
+
if (authKey) requireCloud(cloud); else requireSession(cloud);
|
|
225
|
+
const authHeader = authKey ? { 'x-teamclaude-key': authKey } : { Authorization: `Bearer ${cloud.session.access_token}` };
|
|
223
226
|
const usageOf = (uuid) => {
|
|
224
227
|
if (!usageByUuid || !uuid) return undefined;
|
|
225
228
|
const u = usageByUuid instanceof Map ? usageByUuid.get(uuid) : usageByUuid[uuid];
|
|
@@ -239,7 +242,7 @@ export async function cloudPush(cloud, accounts, usageByUuid = null) {
|
|
|
239
242
|
};
|
|
240
243
|
const res = await httpJson(`${fnBase(cloud)}/sync/push`, {
|
|
241
244
|
method: 'POST',
|
|
242
|
-
headers: fnHeaders(cloud,
|
|
245
|
+
headers: fnHeaders(cloud, authHeader),
|
|
243
246
|
body: payload,
|
|
244
247
|
});
|
|
245
248
|
if (!res.ok) throw new Error(`Cloud push failed (${res.status}): ${describe(res.data)}`);
|
package/src/index.js
CHANGED
|
@@ -315,15 +315,17 @@ async function serverCommand() {
|
|
|
315
315
|
// running AccountManager's LIVE tokens (freshest) and re-reads the cloud session
|
|
316
316
|
// from disk so a `cloud login` done after the server started is picked up.
|
|
317
317
|
cloudPush: config.cloud ? (async () => {
|
|
318
|
-
|
|
319
|
-
const cloud = await ensureCloudSession(fresh);
|
|
320
|
-
if (!cloud?.session?.access_token) throw new Error('Not logged in to cloud — run `teamclaude cloud login`');
|
|
318
|
+
// Use the running server's LIVE tokens (freshest) for each account.
|
|
321
319
|
const accounts = (config.accounts || []).filter(a => a.type !== 'apikey').map(a => {
|
|
322
320
|
const am = (a.accountUuid && accountManager.accounts.find(x => x.accountUuid === a.accountUuid))
|
|
323
321
|
|| accountManager.accounts.find(x => x.name === a.name);
|
|
324
322
|
return am ? { ...a, accessToken: am.credential, refreshToken: am.refreshToken, expiresAt: am.expiresAt } : a;
|
|
325
323
|
});
|
|
326
324
|
if (!accounts.length) throw new Error('No OAuth accounts to push');
|
|
325
|
+
// Prefer the AUTH KEY (push to your own tenant, no expiring owner login) —
|
|
326
|
+
// it CREATES + updates accounts. Fall back to an owner session only if no key.
|
|
327
|
+
const fresh = (await loadConfig()) || config;
|
|
328
|
+
const key = fresh.cloud?.pullKey || config.cloud?.pullKey;
|
|
327
329
|
const usageByUuid = new Map();
|
|
328
330
|
const quotaCache = await readQuotaCache();
|
|
329
331
|
for (const entry of quotaCache?.accounts || []) {
|
|
@@ -331,7 +333,14 @@ async function serverCommand() {
|
|
|
331
333
|
const usage = buildUsageFromQuota(entry);
|
|
332
334
|
if (usage) usageByUuid.set(entry.accountUuid, usage);
|
|
333
335
|
}
|
|
334
|
-
|
|
336
|
+
let cloud, authKey = null;
|
|
337
|
+
if (key) { cloud = resolveCloud(fresh); authKey = key; }
|
|
338
|
+
else {
|
|
339
|
+
cloud = await ensureCloudSession(fresh);
|
|
340
|
+
if (!cloud?.session?.access_token) throw new Error('No auth key and not logged in — pull once with a key, or run `teamclaude cloud login`');
|
|
341
|
+
}
|
|
342
|
+
const r = await cloudPush(cloud, accounts, usageByUuid, authKey);
|
|
343
|
+
return { summary: `${r.pushed} new, ${r.updated} updated, ${r.skipped} skipped${usageByUuid.size ? `, usage ${r.usage ?? 0}` : ''}${authKey ? ' (via auth key)' : ''}` };
|
|
335
344
|
}) : undefined,
|
|
336
345
|
});
|
|
337
346
|
hooks = {
|
|
@@ -894,7 +903,7 @@ async function accountsCommand() {
|
|
|
894
903
|
a.refreshToken = newTokens.refreshToken;
|
|
895
904
|
a.expiresAt = newTokens.expiresAt;
|
|
896
905
|
configDirty = true;
|
|
897
|
-
} catch
|
|
906
|
+
} catch {
|
|
898
907
|
// refresh failed — fetchProfile will report the specific error
|
|
899
908
|
}
|
|
900
909
|
}));
|
|
@@ -1578,6 +1587,17 @@ async function ensureCloudSession(config) {
|
|
|
1578
1587
|
// atomicConfigUpdate's documented cross-process limitation).
|
|
1579
1588
|
await _cloudSessionRefresh;
|
|
1580
1589
|
}
|
|
1590
|
+
// If the session is (still) expired and cannot be refreshed — a Google/SSO token
|
|
1591
|
+
// login (`cloud login --token`) stores NO refresh token, so its ~1h token simply
|
|
1592
|
+
// lapses — fail with an actionable message instead of letting the caller hit a
|
|
1593
|
+
// bare 401 unauthorized.
|
|
1594
|
+
const curExpMs = (Number(cloud.session.expires_at) || 0) * 1000;
|
|
1595
|
+
if (curExpMs && Date.now() >= curExpMs) {
|
|
1596
|
+
const hint = cloud.session.refresh_token
|
|
1597
|
+
? 'Run `teamclaude cloud login` to sign in again.'
|
|
1598
|
+
: 'Re-copy the CLI login token from the dashboard (the "관리자 로그인" box in the Auth Keys section) and run `teamclaude cloud login --token`.';
|
|
1599
|
+
throw new Error(`Cloud session expired. ${hint}`);
|
|
1600
|
+
}
|
|
1581
1601
|
return cloud;
|
|
1582
1602
|
}
|
|
1583
1603
|
|
|
@@ -1706,13 +1726,29 @@ async function cloudLoginCommand(config) {
|
|
|
1706
1726
|
}
|
|
1707
1727
|
|
|
1708
1728
|
async function cloudPushCommand(config) {
|
|
1709
|
-
const cloud = await ensureCloudSession(config);
|
|
1710
|
-
if (!cloud?.session?.access_token) { console.error('Not logged in. Run `teamclaude cloud login`.'); process.exit(1); }
|
|
1711
1729
|
const accounts = (config.accounts || []).filter(a => a.type !== 'apikey');
|
|
1712
1730
|
if (!accounts.length) { console.error('No OAuth accounts to push.'); process.exit(1); }
|
|
1713
|
-
//
|
|
1714
|
-
//
|
|
1715
|
-
//
|
|
1731
|
+
// Prefer the AUTH KEY: pushing to your own tenant is the key's job — no separate,
|
|
1732
|
+
// short-lived owner login required (the key is the credential, same as pull). This
|
|
1733
|
+
// CREATES + updates accounts in the key's owner tenant (freshest-wins + plan limits).
|
|
1734
|
+
const key = config.cloud?.pullKey || process.env.TEAMCLAUDE_AUTH_KEY;
|
|
1735
|
+
if (key) {
|
|
1736
|
+
const usageByUuid = new Map();
|
|
1737
|
+
const quotaCache = await readQuotaCache();
|
|
1738
|
+
for (const entry of quotaCache?.accounts || []) {
|
|
1739
|
+
if (!entry?.accountUuid) continue;
|
|
1740
|
+
const usage = buildUsageFromQuota(entry);
|
|
1741
|
+
if (usage) usageByUuid.set(entry.accountUuid, usage);
|
|
1742
|
+
}
|
|
1743
|
+
const r = await cloudPush(resolveCloud(config), accounts, usageByUuid, key);
|
|
1744
|
+
const usageNote = usageByUuid.size ? `, usage ${r.usage ?? 0}/${usageByUuid.size}` : '';
|
|
1745
|
+
console.log(`Pushed to your account via auth key: ${r.pushed} new, ${r.updated} updated, ${r.skipped} skipped${usageNote}.`);
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1748
|
+
// No auth key — fall back to an owner login (which also CREATES new accounts) and
|
|
1749
|
+
// rides the quota snapshot so the dashboard can show utilization.
|
|
1750
|
+
const cloud = await ensureCloudSession(config);
|
|
1751
|
+
if (!cloud?.session?.access_token) { console.error('No auth key found and not logged in. Pull once with `teamclaude cloud pull --key <key>`, or `teamclaude cloud login`.'); process.exit(1); }
|
|
1716
1752
|
const usageByUuid = new Map();
|
|
1717
1753
|
const quotaCache = await readQuotaCache();
|
|
1718
1754
|
for (const entry of quotaCache?.accounts || []) {
|
package/src/tui.js
CHANGED
|
@@ -399,8 +399,7 @@ export class TUI {
|
|
|
399
399
|
try {
|
|
400
400
|
this._addLog('Pushing account tokens to the cloud...');
|
|
401
401
|
const r = await this.cloudPush();
|
|
402
|
-
|
|
403
|
-
this._addLog(`Cloud push: ${r?.pushed ?? 0} new, ${r?.updated ?? 0} updated, ${r?.skipped ?? 0} skipped${usageNote}`);
|
|
402
|
+
this._addLog(`Cloud push: ${r?.summary ?? 'done'}`);
|
|
404
403
|
} catch (e) {
|
|
405
404
|
this._addLog(`Cloud push failed: ${e.message}`);
|
|
406
405
|
}
|