@rikcodes/teamclaude 1.1.22-rik.4 → 1.1.22-rik.6
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/config.js +1 -0
- package/src/index.js +3 -1
- package/src/tui.js +94 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rikcodes/teamclaude",
|
|
3
|
-
"version": "1.1.22-rik.
|
|
3
|
+
"version": "1.1.22-rik.6",
|
|
4
4
|
"description": "Multi-account proxy for Claude Code and Codex: pools Claude Max, ChatGPT/Codex, API-key and third-party backend accounts, and rotates on quota",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/config.js
CHANGED
|
@@ -111,6 +111,7 @@ export function createDefaultConfig() {
|
|
|
111
111
|
// the proxy does no throughput work at all (see OutputTracker).
|
|
112
112
|
throughputMeter: false,
|
|
113
113
|
projection: { enabled: true, windowMinutes: 90, wasteFloor: 0.1 },
|
|
114
|
+
accountSort: 'arranged',
|
|
114
115
|
eventLogging: 'hide',
|
|
115
116
|
defaultClientMode: 'mitm',
|
|
116
117
|
// Written out rather than left absent, so a fresh config states the one
|
package/src/index.js
CHANGED
|
@@ -37,7 +37,7 @@ import { ResetCreditRedeemer } from './codex-reset-credits.js';
|
|
|
37
37
|
import { Warmer } from './warmer.js';
|
|
38
38
|
import { formatWarmupScheduleConfirmation, resolveWarmupConfig } from './warmup-schedule.js';
|
|
39
39
|
import { Sidecar, SIDECAR_DRAIN_GRACE_MS, SIDECAR_SHUTDOWN_GRACE_MS } from './sidecar.js';
|
|
40
|
-
import { TUI } from './tui.js';
|
|
40
|
+
import { TUI, ACCOUNT_SORTS } from './tui.js';
|
|
41
41
|
import { SessionTitles } from './session-titles.js';
|
|
42
42
|
import { captureEarlyConsole } from './early-log.js';
|
|
43
43
|
import { RemoteControl, createAttachSession } from './tui-remote.js';
|
|
@@ -535,6 +535,7 @@ async function serverCommand() {
|
|
|
535
535
|
// Read by the server per request and by the TUI per frame, so the reload
|
|
536
536
|
// is the whole application: the next request is timed, or is not.
|
|
537
537
|
config.throughputMeter = diskConfig.throughputMeter === true;
|
|
538
|
+
config.accountSort = ACCOUNT_SORTS.includes(diskConfig.accountSort) ? diskConfig.accountSort : 'arranged';
|
|
538
539
|
// Read by `run`/`env` from disk, but the TUI settings screen shows it live.
|
|
539
540
|
config.defaultClientMode = diskConfig.defaultClientMode === 'base-url' ? 'base-url' : 'mitm';
|
|
540
541
|
// The fleet switch for spending Codex reset credits. The redeemer reads it
|
|
@@ -636,6 +637,7 @@ async function serverCommand() {
|
|
|
636
637
|
if (config.eventLogging != null) diskConfig.eventLogging = config.eventLogging;
|
|
637
638
|
if (config.quotaBarPercent != null) diskConfig.quotaBarPercent = config.quotaBarPercent;
|
|
638
639
|
if (config.throughputMeter != null) diskConfig.throughputMeter = config.throughputMeter;
|
|
640
|
+
if (config.accountSort != null) diskConfig.accountSort = config.accountSort;
|
|
639
641
|
if (config.defaultClientMode != null) diskConfig.defaultClientMode = config.defaultClientMode;
|
|
640
642
|
if (config.autoRedeemResets != null) diskConfig.autoRedeemResets = config.autoRedeemResets;
|
|
641
643
|
if (config.blockedModels != null) diskConfig.blockedModels = config.blockedModels;
|
package/src/tui.js
CHANGED
|
@@ -286,6 +286,38 @@ const PANE_MIN = 62;
|
|
|
286
286
|
// the feature, and there is nothing to migrate.
|
|
287
287
|
const listRank = (/** @type {any} */ a) => (Number.isFinite(a?.displayOrder) ? a.displayOrder : Infinity);
|
|
288
288
|
|
|
289
|
+
// How the account list is ordered inside each provider group (`accountSort`).
|
|
290
|
+
// `arranged` is the operator's own order (listRank). Each of the others puts
|
|
291
|
+
// the account whose window ends soonest first, so quota that is about to
|
|
292
|
+
// expire unspent is at the top of the list.
|
|
293
|
+
//
|
|
294
|
+
// The window each sort reads. S7 and F7 are the family's own weekly bucket
|
|
295
|
+
// where the account has one, else the all-models weekly, which is what governs
|
|
296
|
+
// that family on such an account — the rule quota-summary.js resolves a
|
|
297
|
+
// family's window by.
|
|
298
|
+
/** @type {Record<string, (q: any) => any>} */
|
|
299
|
+
const SORT_RESET = {
|
|
300
|
+
'session-reset': q => q.unified5hReset,
|
|
301
|
+
'weekly-reset': q => q.unified7dReset,
|
|
302
|
+
'sonnet-reset': q => (q.unified7dSonnet != null ? q.unified7dSonnetReset : q.unified7dReset),
|
|
303
|
+
'fable-reset': q => (q.unified7dFable != null ? q.unified7dFableReset : q.unified7dReset),
|
|
304
|
+
};
|
|
305
|
+
export const ACCOUNT_SORTS = ['arranged', ...Object.keys(SORT_RESET)];
|
|
306
|
+
/** @type {Record<string, string>} */
|
|
307
|
+
const ACCOUNT_SORT_LABELS = {
|
|
308
|
+
arranged: 'arranged',
|
|
309
|
+
'session-reset': 'session reset',
|
|
310
|
+
'weekly-reset': 'weekly reset',
|
|
311
|
+
'sonnet-reset': 'S7 reset',
|
|
312
|
+
'fable-reset': 'F7 reset',
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// The sort key: when the window resets. No reading (an API-key account, one
|
|
316
|
+
// that has not reported, a five-hour window nothing has opened) and a reset
|
|
317
|
+
// that has already passed both sort last: the second is a window that has just
|
|
318
|
+
// started over, so its next reset is the one furthest away.
|
|
319
|
+
const resetRank = (/** @type {any} */ t, /** @type {number} */ now) => (Number.isFinite(t) && t > now ? t : Infinity);
|
|
320
|
+
|
|
289
321
|
// How long a reorder waits after the last move before it is written. Longer
|
|
290
322
|
// than a terminal's key-repeat interval, so a held arrow is one write; short
|
|
291
323
|
// enough that the file is current by the time anyone looks at it.
|
|
@@ -1468,6 +1500,21 @@ export class TUI {
|
|
|
1468
1500
|
});
|
|
1469
1501
|
}
|
|
1470
1502
|
|
|
1503
|
+
if (this.am.accounts.length > 1) {
|
|
1504
|
+
fields.push({
|
|
1505
|
+
id: 'accountSort',
|
|
1506
|
+
label: 'Sort accounts',
|
|
1507
|
+
hint: '←→ cycle',
|
|
1508
|
+
value: () => {
|
|
1509
|
+
const s = this._accountSort();
|
|
1510
|
+
return s === 'arranged' ? gray(ACCOUNT_SORT_LABELS[s]) : green(ACCOUNT_SORT_LABELS[s]);
|
|
1511
|
+
},
|
|
1512
|
+
left: () => this._cycleAccountSort(-1),
|
|
1513
|
+
right: () => this._cycleAccountSort(+1),
|
|
1514
|
+
enter: () => this._cycleAccountSort(+1),
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1471
1518
|
fields.push({
|
|
1472
1519
|
id: 'upstreamProxy',
|
|
1473
1520
|
label: 'Upstream proxy',
|
|
@@ -2093,6 +2140,24 @@ export class TUI {
|
|
|
2093
2140
|
if (this.running) this.render();
|
|
2094
2141
|
}
|
|
2095
2142
|
|
|
2143
|
+
/** The configured account sort; anything unknown reads as `arranged`. */
|
|
2144
|
+
_accountSort() {
|
|
2145
|
+
const s = this.config?.accountSort;
|
|
2146
|
+
return ACCOUNT_SORTS.includes(s) ? s : 'arranged';
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
async _cycleAccountSort(dir = 1) {
|
|
2150
|
+
// Read by _displayOrder on every frame, so the assignment is the whole
|
|
2151
|
+
// application and the save is only what survives a restart.
|
|
2152
|
+
const prev = this.config.accountSort;
|
|
2153
|
+
const cur = this._accountSort();
|
|
2154
|
+
const next = ACCOUNT_SORTS[(ACCOUNT_SORTS.indexOf(cur) + dir + ACCOUNT_SORTS.length) % ACCOUNT_SORTS.length];
|
|
2155
|
+
this.config.accountSort = next;
|
|
2156
|
+
if (!await this._saveSetting('account sort', () => { this.config.accountSort = prev; })) return;
|
|
2157
|
+
this._addLog(`Account sort: ${ACCOUNT_SORT_LABELS[next]}`);
|
|
2158
|
+
if (this.running) this.render();
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2096
2161
|
async _cycleEventLogging(dir = 1) {
|
|
2097
2162
|
// Claude Code telemetry display/handling: show → hide → block → show.
|
|
2098
2163
|
const order = ['show', 'hide', 'block'];
|
|
@@ -2983,17 +3048,32 @@ export class TUI {
|
|
|
2983
3048
|
* rows — see _keySelect, which walks this order but still stores an index.
|
|
2984
3049
|
* Which is also why the arrangement is a sort key rather than a permutation
|
|
2985
3050
|
* of `am.accounts`: see _doMoveAccount.
|
|
3051
|
+
*
|
|
3052
|
+
* With a reset sort (`accountSort`) the soonest reset goes before the
|
|
3053
|
+
* arrangement, which then only breaks ties. Not on the reorder screen, and
|
|
3054
|
+
* not when `arranged` is asked for: the arrangement is what that screen
|
|
3055
|
+
* edits, so it must see that order.
|
|
3056
|
+
*
|
|
3057
|
+
* @param {{ arranged?: boolean }} [opts]
|
|
2986
3058
|
*/
|
|
2987
|
-
_displayOrder() {
|
|
3059
|
+
_displayOrder({ arranged = false } = {}) {
|
|
3060
|
+
const resetOf = arranged || (this.mode === 'select' && this.selAction === 'reorder')
|
|
3061
|
+
? null : SORT_RESET[this._accountSort()];
|
|
3062
|
+
const now = Date.now();
|
|
2988
3063
|
return this.am.accounts
|
|
2989
3064
|
.map((/** @type {any} */ _, /** @type {number} */ i) => i)
|
|
2990
3065
|
.filter(i => !isLocalUpstream(this.am.accounts[i]))
|
|
2991
3066
|
.sort((/** @type {number} */ x, /** @type {number} */ y) => {
|
|
2992
3067
|
const px = PROVIDER_ORDER.indexOf(providerOf(this.am.accounts[x]));
|
|
2993
3068
|
const py = PROVIDER_ORDER.indexOf(providerOf(this.am.accounts[y]));
|
|
2994
|
-
// Provider, then the arrangement: the provider is what a
|
|
2995
|
-
// number the operator set
|
|
3069
|
+
// Provider, then the sort, then the arrangement: the provider is what a
|
|
3070
|
+
// row IS, so no sort and no number the operator set crosses it.
|
|
2996
3071
|
if (px !== py) return px - py;
|
|
3072
|
+
if (resetOf) {
|
|
3073
|
+
const tx = resetRank(resetOf(this.am.accounts[x].quota || {}), now);
|
|
3074
|
+
const ty = resetRank(resetOf(this.am.accounts[y].quota || {}), now);
|
|
3075
|
+
if (tx !== ty) return tx < ty ? -1 : 1; // Infinity - Infinity is NaN, so compare
|
|
3076
|
+
}
|
|
2997
3077
|
const rx = listRank(this.am.accounts[x]);
|
|
2998
3078
|
const ry = listRank(this.am.accounts[y]);
|
|
2999
3079
|
// Infinity !== Infinity is false, so two unplaced accounts fall through
|
|
@@ -3281,9 +3361,13 @@ export class TUI {
|
|
|
3281
3361
|
* Every account except the locally-served ones. _displayOrder already leaves
|
|
3282
3362
|
* those out (they draw as conduit lines, not rows), so they hold no position
|
|
3283
3363
|
* and their array slots are simply stepped over.
|
|
3364
|
+
*
|
|
3365
|
+
* Always in the arranged order, whatever `accountSort` says: a move
|
|
3366
|
+
* renumbers every account from this list, so a sorted list here would
|
|
3367
|
+
* write the sort into `displayOrder`.
|
|
3284
3368
|
*/
|
|
3285
3369
|
_arrangeable() {
|
|
3286
|
-
return this._displayOrder();
|
|
3370
|
+
return this._displayOrder({ arranged: true });
|
|
3287
3371
|
}
|
|
3288
3372
|
|
|
3289
3373
|
/** The rows that carry ►: the cursor, or in a mixed pool each provider's current
|
|
@@ -3624,6 +3708,12 @@ export class TUI {
|
|
|
3624
3708
|
lines.push(row(byId('addAccount')));
|
|
3625
3709
|
if (byId('removeAccount')) lines.push(row(byId('removeAccount')));
|
|
3626
3710
|
if (byId('orderAccounts')) lines.push(row(byId('orderAccounts')));
|
|
3711
|
+
if (byId('accountSort')) {
|
|
3712
|
+
lines.push(row(byId('accountSort')));
|
|
3713
|
+
lines.push(dim(' A reset sort lists the account whose window ends soonest first;'));
|
|
3714
|
+
lines.push(dim(' S7/F7 read the weekly window on an account without one. The'));
|
|
3715
|
+
lines.push(dim(' arranged order breaks ties.'));
|
|
3716
|
+
}
|
|
3627
3717
|
lines.push('');
|
|
3628
3718
|
// ── Network
|
|
3629
3719
|
// Drawn before the sx.org block, which returns early when sx is unavailable:
|