teamclaude-cloud 1.5.0 → 1.5.1

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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +24 -0
  3. package/src/tui.js +22 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamclaude-cloud",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "Multi-account Claude proxy with quota-based rotation + cloud token sync and auth-key control",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -309,6 +309,30 @@ async function serverCommand() {
309
309
  if (disabled) set.add(uuid); else set.delete(uuid);
310
310
  cloud.localDisabled = [...set];
311
311
  }),
312
+ // `p` in the dashboard = `teamclaude cloud push`, so the admin can register the
313
+ // server's live account tokens to the cloud without dropping to a shell. Only
314
+ // wired when a cloud config exists (else the footer hides the key). Uses the
315
+ // running AccountManager's LIVE tokens (freshest) and re-reads the cloud session
316
+ // from disk so a `cloud login` done after the server started is picked up.
317
+ cloudPush: config.cloud ? (async () => {
318
+ const fresh = (await loadConfig()) || config;
319
+ const cloud = await ensureCloudSession(fresh);
320
+ if (!cloud?.session?.access_token) throw new Error('Not logged in to cloud — run `teamclaude cloud login`');
321
+ const accounts = (config.accounts || []).filter(a => a.type !== 'apikey').map(a => {
322
+ const am = (a.accountUuid && accountManager.accounts.find(x => x.accountUuid === a.accountUuid))
323
+ || accountManager.accounts.find(x => x.name === a.name);
324
+ return am ? { ...a, accessToken: am.credential, refreshToken: am.refreshToken, expiresAt: am.expiresAt } : a;
325
+ });
326
+ if (!accounts.length) throw new Error('No OAuth accounts to push');
327
+ const usageByUuid = new Map();
328
+ const quotaCache = await readQuotaCache();
329
+ for (const entry of quotaCache?.accounts || []) {
330
+ if (!entry?.accountUuid) continue;
331
+ const usage = buildUsageFromQuota(entry);
332
+ if (usage) usageByUuid.set(entry.accountUuid, usage);
333
+ }
334
+ return await cloudPush(cloud, accounts, usageByUuid);
335
+ }) : undefined,
312
336
  });
313
337
  hooks = {
314
338
  onRequestStart: (id, info) => tui.onRequestStart(id, info),
package/src/tui.js CHANGED
@@ -114,7 +114,7 @@ function timestamp() {
114
114
  // ── TUI class ────────────────────────────────────────────────
115
115
 
116
116
  export class TUI {
117
- constructor({ accountManager, config, saveConfig, syncAccounts, refreshQuota, onQuit, setLocalDisabled }) {
117
+ constructor({ accountManager, config, saveConfig, syncAccounts, refreshQuota, onQuit, setLocalDisabled, cloudPush }) {
118
118
  this.am = accountManager;
119
119
  this.config = config;
120
120
  this.saveConfig = saveConfig;
@@ -122,6 +122,7 @@ export class TUI {
122
122
  this.refreshQuota = refreshQuota; // optional: forced fleet quota re-measure (R)
123
123
  this.onQuit = onQuit;
124
124
  this.setLocalDisabled = setLocalDisabled; // optional: persist offline local-disable overlay (by uuid) to disk (fresh-read merge)
125
+ this.cloudPush = cloudPush; // optional: push account tokens to the cloud (`p`) — returns {pushed,updated,skipped,usage}
125
126
 
126
127
  this.log = []; // completed activity entries
127
128
  this.active = new Map(); // in-flight requests
@@ -263,6 +264,7 @@ export class TUI {
263
264
  if (k === 'q') { this.stop(); this.onQuit?.(); return; }
264
265
  if (k === 'a') { this.mode = 'add'; return; }
265
266
  if (k === 'R') { this._doSync(); return; }
267
+ if (k === 'p') { this._doPush(); return; }
266
268
 
267
269
  if (this.am.accounts.length === 0) return;
268
270
 
@@ -390,6 +392,20 @@ export class TUI {
390
392
  }
391
393
  }
392
394
 
395
+ // `p` — push this machine's account tokens (+ usage snapshot) to the cloud, the
396
+ // same as `teamclaude push` / `teamclaude cloud push`, without leaving the dashboard.
397
+ async _doPush() {
398
+ if (!this.cloudPush) { this._addLog('Cloud push unavailable (no cloud config)'); return; }
399
+ try {
400
+ this._addLog('Pushing account tokens to the cloud...');
401
+ const r = await this.cloudPush();
402
+ const usageNote = (r && r.usage != null) ? `, usage ${r.usage}` : '';
403
+ this._addLog(`Cloud push: ${r?.pushed ?? 0} new, ${r?.updated ?? 0} updated, ${r?.skipped ?? 0} skipped${usageNote}`);
404
+ } catch (e) {
405
+ this._addLog(`Cloud push failed: ${e.message}`);
406
+ }
407
+ }
408
+
393
409
  async _doImport() {
394
410
  try {
395
411
  this._addLog('Importing credentials...');
@@ -835,8 +851,11 @@ export class TUI {
835
851
 
836
852
  _renderFooter() {
837
853
  switch (this.mode) {
838
- case 'normal':
839
- return ` ${dim('↑↓')} select ${bold('s')}witch ${bold('e')}nable/disable ${bold('o')}rder ${bold('d')}elete ${bold('a')}dd ${bold('R')}eload ${bold('q')}uit`;
854
+ case 'normal': {
855
+ // `p`ush is only shown when a cloud config is present (the callback is wired).
856
+ const push = this.cloudPush ? ` ${bold('p')}ush` : '';
857
+ return ` ${dim('↑↓')} select ${bold('s')}witch ${bold('e')}nable/disable ${bold('o')}rder ${bold('d')}elete ${bold('a')}dd${push} ${bold('R')}eload ${bold('q')}uit`;
858
+ }
840
859
  case 'select':
841
860
  return ` ${dim('↑↓')} select ${bold('Enter')} delete ${bold('Esc')} cancel`;
842
861
  case 'order':