teamclaude-cloud 1.7.0 → 1.8.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamclaude-cloud",
3
- "version": "1.7.0",
3
+ "version": "1.8.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",
@@ -61,10 +61,11 @@ export class AccountManager {
61
61
  // means "no preference" — selection then falls back to use-or-lose. So a
62
62
  // config with no priorities behaves exactly as before.
63
63
  priority: Number.isFinite(acct.priority) ? Math.floor(acct.priority) : null,
64
- // Provenance: an account pulled from the cloud carries one or more managing
65
- // group source tags (cloudSources, set by mergePulledAccounts). Surfaced so
66
- // the dashboard/status can label it "cloud" vs a locally-added account.
67
- fromCloud: Array.isArray(acct.cloudSources) && acct.cloudSources.length > 0,
64
+ // Provenance: an account that belongs to a personal (private) cloud group
65
+ // carries cloudPrivate (set by mergePulledAccounts from the pull response's
66
+ // `personal` flag). Surfaced so the dashboard/status can label it "private"
67
+ // vs a shared/fleet account.
68
+ isPrivate: acct.cloudPrivate === true,
68
69
  quota: emptyQuota(),
69
70
  usage: {
70
71
  totalInputTokens: 0,
@@ -1058,7 +1059,7 @@ export class AccountManager {
1058
1059
  status: 'active',
1059
1060
  enabled: acctData.enabled !== false,
1060
1061
  priority: Number.isFinite(acctData.priority) ? Math.floor(acctData.priority) : null,
1061
- fromCloud: Array.isArray(acctData.cloudSources) && acctData.cloudSources.length > 0,
1062
+ isPrivate: acctData.cloudPrivate === true,
1062
1063
  quota: emptyQuota(),
1063
1064
  usage: { totalInputTokens: 0, totalOutputTokens: 0, totalRequests: 0, lastUsed: null },
1064
1065
  rateLimitedUntil: null,
@@ -1259,7 +1260,7 @@ export class AccountManager {
1259
1260
  type: a.type,
1260
1261
  status: a.status,
1261
1262
  enabled: a.enabled !== false,
1262
- fromCloud: a.fromCloud === true,
1263
+ isPrivate: a.isPrivate === true,
1263
1264
  priority: a.priority ?? null,
1264
1265
  // Deep-copy the nested modelWeekly map — the shallow quota spread would
1265
1266
  // otherwise hand callers a live reference into account state.
package/src/cloud.js CHANGED
@@ -593,6 +593,14 @@ export function mergePulledAccounts(config, pulled, source = null, allowed = nul
593
593
  if (Number.isInteger(incoming.priority)) target.priority = incoming.priority;
594
594
  else delete target.priority; // null → automatic ordering
595
595
  };
596
+ // Personal-group (private) display flag from the pull response. Authoritative when
597
+ // the server sends `personal` (a pull can move an account in/out of a personal
598
+ // group); left untouched on a pre-personal server response so it isn't wiped.
599
+ const applyPrivate = (target, incoming) => {
600
+ if (!('personal' in incoming)) return;
601
+ if (incoming.personal === true) target.cloudPrivate = true;
602
+ else delete target.cloudPrivate;
603
+ };
596
604
  // Multi-source tagging: an account can be managed by several keys (a consumer
597
605
  // using >1 key whose groups overlap). Each key adds its source; an account is
598
606
  // only pruned once NO managing key still allows it (Codex round 4 R4-3).
@@ -624,6 +632,7 @@ export function mergePulledAccounts(config, pulled, source = null, allowed = nul
624
632
  applyOverlay(fresh); // honor an offline local-disable that survived a prune+re-add
625
633
  addSource(fresh);
626
634
  applyPriority(fresh, a);
635
+ applyPrivate(fresh, a);
627
636
  config.accounts.push(fresh);
628
637
  summary.added++;
629
638
  continue;
@@ -638,9 +647,9 @@ export function mergePulledAccounts(config, pulled, source = null, allowed = nul
638
647
  addSource(cur); // now managed by this key (so this key can prune it later)
639
648
  // A payload entry with no token at all (allowed-but-tokenless) must not clobber
640
649
  // a valid local token — only apply priority/metadata, keep the local token.
641
- if (!hasAnyToken(a)) { applyPriority(cur, a); summary.skipped++; continue; }
650
+ if (!hasAnyToken(a)) { applyPriority(cur, a); applyPrivate(cur, a); summary.skipped++; continue; }
642
651
  const curExp = normalizeExpiresAt(cur.expiresAt) || 0;
643
- if (curExp > incomingExp) { applyPriority(cur, a); summary.skipped++; continue; } // local token fresher — keep it
652
+ if (curExp > incomingExp) { applyPriority(cur, a); applyPrivate(cur, a); summary.skipped++; continue; } // local token fresher — keep it
644
653
  config.accounts[idx] = {
645
654
  ...cur,
646
655
  accountUuid: a.accountUuid || cur.accountUuid,
@@ -654,6 +663,7 @@ export function mergePulledAccounts(config, pulled, source = null, allowed = nul
654
663
  };
655
664
  applyOverlay(config.accounts[idx]); // re-assert overlay on the rebuilt row
656
665
  applyPriority(config.accounts[idx], a);
666
+ applyPrivate(config.accounts[idx], a);
657
667
  summary.updated++;
658
668
  }
659
669
  // Prune: only when the server sent an authoritative allow-set (`allowed`). Never
package/src/index.js CHANGED
@@ -892,8 +892,8 @@ async function statusCommand() {
892
892
  const current = acct.name === data.currentAccount ? ' *' : '';
893
893
 
894
894
  const disabledTag = acct.enabled === false ? ' [disabled]' : '';
895
- const cloudTag = acct.fromCloud ? ' [cloud]' : '';
896
- console.log(` ${maskIf(acct.name, config.maskMode)} (${acct.type})${current}${cloudTag}${disabledTag}`);
895
+ const privateTag = acct.isPrivate ? ' [🔑 private]' : '';
896
+ console.log(` ${maskIf(acct.name, config.maskMode)} (${acct.type})${current}${privateTag}${disabledTag}`);
897
897
  console.log(` Status: ${acct.status}${acct.enabled === false ? ' (disabled — out of rotation)' : ''}`);
898
898
  if (acct.priority != null) console.log(` Priority: ${acct.priority} (lower = preferred)`);
899
899
  if (acct.maxConcurrent != null) {
@@ -1347,9 +1347,9 @@ async function syncAccountsFromDisk(diskConfig, memConfig, accountManager) {
1347
1347
  if (mgr.enabled !== wantEnabled) accountManager.setEnabled(mgr, wantEnabled);
1348
1348
  const diskPriority = Number.isFinite(diskAcct.priority) ? Math.floor(diskAcct.priority) : null;
1349
1349
  if (mgr.priority !== diskPriority) accountManager.setPriority(mgr, diskPriority);
1350
- // Provenance can change while running (a cloud pull tags/untags an account),
1351
- // so refresh the cloud flag on reload too.
1352
- mgr.fromCloud = Array.isArray(diskAcct.cloudSources) && diskAcct.cloudSources.length > 0;
1350
+ // Provenance can change while running (a cloud pull moves an account in/out of
1351
+ // a personal group), so refresh the private flag on reload too.
1352
+ mgr.isPrivate = diskAcct.cloudPrivate === true;
1353
1353
  // Mirror the applied state into the in-memory config copy too. Otherwise a
1354
1354
  // later TUI saveConfig (for any unrelated op) would spread the pre-sync
1355
1355
  // enabled/priority over the disk value and silently revert a CLI change.
@@ -1357,15 +1357,20 @@ async function syncAccountsFromDisk(diskConfig, memConfig, accountManager) {
1357
1357
  if (memAcct) {
1358
1358
  if (wantEnabled) delete memAcct.enabled; else memAcct.enabled = false;
1359
1359
  if (diskPriority === null) delete memAcct.priority; else memAcct.priority = diskPriority;
1360
- // cloudSources is authoritative on disk (a cloud pull tags/untags it) and
1361
- // is an access-control signal. Mirror it too, or a later TUI saveConfig
1362
- // would overlay the stale in-memory array onto disk and resurrect a
1363
- // source tag a pull just removed silently defeating a cloud revocation.
1360
+ // cloudSources (prune scoping) and cloudPrivate (personal-group display) are
1361
+ // both authoritative on disk a cloud pull tags/untags them. Mirror both, or
1362
+ // a later TUI saveConfig would overlay the stale in-memory value onto disk
1363
+ // and resurrect a source tag / private flag a pull just changed.
1364
1364
  if (Array.isArray(diskAcct.cloudSources) && diskAcct.cloudSources.length > 0) {
1365
1365
  memAcct.cloudSources = diskAcct.cloudSources;
1366
1366
  } else {
1367
1367
  delete memAcct.cloudSources;
1368
1368
  }
1369
+ if (diskAcct.cloudPrivate === true) {
1370
+ memAcct.cloudPrivate = true;
1371
+ } else {
1372
+ delete memAcct.cloudPrivate;
1373
+ }
1369
1374
  }
1370
1375
  }
1371
1376
 
package/src/tui.js CHANGED
@@ -18,6 +18,9 @@ const yellow = s => fg(33, s);
18
18
  const red = s => fg(31, s);
19
19
  const cyan = s => fg(36, s);
20
20
  const gray = s => fg(90, s);
21
+ // A pill-style badge: light-cyan text on a dark-teal background with padding, so it
22
+ // reads as a badge (like the web dashboard's) rather than plain colored text.
23
+ const badge = label => `${ESC}48;5;23m${ESC}38;5;159m ${label} ${RESET}`;
21
24
 
22
25
  const ANSI_RE = /\x1b\[[0-9;]*m/g;
23
26
  const strip = s => s.replace(ANSI_RE, '');
@@ -861,10 +864,11 @@ export class TUI {
861
864
  // badges stay column-aligned across mixed account types.
862
865
  line += l3 ? ` ${l3} ${bar(r3, bw, t3)}` : ' '.repeat(6 + bw);
863
866
  }
864
- // Provenance badge: an account pulled from the cloud is tagged "cloud" so it
865
- // is distinguishable from one added locally on this machine. Appended (like
866
- // the rank badge) so it never disrupts the fixed-width columns / quota bars.
867
- if (a.fromCloud) line += ` ${cyan('cloud')}`;
867
+ // Provenance badge: an account in a personal (private) cloud group gets a pill
868
+ // badge "🔑 private" (matching the web dashboard) so it's distinguishable from a
869
+ // shared/fleet account. Appended (like the rank badge) so it never disrupts the
870
+ // fixed-width columns / quota bars.
871
+ if (a.isPrivate) line += ` ${badge('🔑 private')}`;
868
872
  // Order badge: ranked accounts show their 1-based position (#1 = most
869
873
  // preferred). While ordering, unranked accounts are labelled "auto" so the
870
874
  // two groups (pinned order vs use-or-lose) are visible. Appended last so it