agy-crew 0.9.2 → 0.9.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/companion/accounts.mjs +18 -12
- package/companion/pool/format.mjs +115 -0
- package/companion/pool/quota.mjs +0 -9
- package/docs/REFERENCE.md +3 -3
- package/docs/REFERENCE.zh-CN.md +3 -3
- package/opencode-skills/agy-crew-accounts/SKILL.md +4 -4
- package/package.json +1 -1
- package/pi-skills/agy-crew-accounts/SKILL.md +4 -4
- package/skills/accounts/SKILL.md +4 -4
package/companion/accounts.mjs
CHANGED
|
@@ -16,7 +16,8 @@ import { accountDir, accountHome, accountSettingsPath, loadPool, poolExists, poo
|
|
|
16
16
|
updatePool, validateAccountName, PoolError } from './pool/state.mjs';
|
|
17
17
|
import { accountInLoan } from './pool/loans.mjs';
|
|
18
18
|
import { readToken } from './pool/token.mjs';
|
|
19
|
-
import { refreshQuotas
|
|
19
|
+
import { refreshQuotas } from './pool/quota.mjs';
|
|
20
|
+
import { formatAgo, renderCooldowns, renderQuotaTable, renderTable } from './pool/format.mjs';
|
|
20
21
|
import { replaceFile } from './state-lock.mjs';
|
|
21
22
|
|
|
22
23
|
const out = (text) => process.stdout.write(text + '\n');
|
|
@@ -47,6 +48,9 @@ function accountState(pool, account, now) {
|
|
|
47
48
|
return n ? `busy ${n}` : 'ready';
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
/** Text-mode state: the per-family cooldown detail lives in the COOLDOWNS section. */
|
|
52
|
+
const stateLabel = (state) => (state.startsWith('cooling ') ? 'cooling' : state);
|
|
53
|
+
|
|
50
54
|
function cmdList(opts) {
|
|
51
55
|
const pool = currentPool();
|
|
52
56
|
const now = Date.now();
|
|
@@ -56,12 +60,14 @@ function cmdList(opts) {
|
|
|
56
60
|
cooldowns: Object.entries(pool.cooldowns).filter(([k]) => k.startsWith(`${a.name}|`)).map(([k, c]) => ({ family: k.split('|')[1], ...c })),
|
|
57
61
|
}));
|
|
58
62
|
if (opts.json) return printJson({ accounts: rows, live: { description: 'your own agy login; never pooled' } });
|
|
59
|
-
if (!rows.length) out(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
63
|
+
if (!rows.length) out('No pool accounts. Add one with `accounts login <name>`.');
|
|
64
|
+
const table = rows.map((r) => [r.name, r.email || 'unknown', stateLabel(r.state), formatAgo(r.last_used_at, now)]);
|
|
65
|
+
table.push({ lead: ['live'], text: 'your own agy login, never pooled' });
|
|
66
|
+
const lines = renderTable(['ACCOUNT', 'EMAIL', 'STATE', 'LAST USED'], table);
|
|
67
|
+
if (rows.length) lines.push('', ...renderQuotaTable(rows.map((r) => ({ name: r.name, snapshot: r.quota })), now));
|
|
68
|
+
const cooling = rows.flatMap((r) => r.cooldowns.filter((c) => Date.parse(c.retry_at) > now).map((c) => ({ name: r.name, ...c })));
|
|
69
|
+
if (cooling.length) lines.push('', ...renderCooldowns(cooling, now));
|
|
70
|
+
out(lines.join('\n'));
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
// ---------------------------------------------------------------------------
|
|
@@ -267,11 +273,11 @@ async function cmdQuota(opts, deps) {
|
|
|
267
273
|
cooldowns: Object.entries(latest.cooldowns).filter(([k, c]) => k.startsWith(`${name}|`) && Date.parse(c.retry_at) > now)
|
|
268
274
|
.map(([k, c]) => ({ family: k.split('|')[1], retry_at: c.retry_at })) }));
|
|
269
275
|
if (opts.json) return printJson({ accounts: rows });
|
|
270
|
-
if (!rows.length) out('No pool accounts. Add one with `accounts login <name>`.');
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
276
|
+
if (!rows.length) return out('No pool accounts. Add one with `accounts login <name>`.');
|
|
277
|
+
const lines = renderQuotaTable(rows.map((r) => ({ name: r.name, snapshot: r.snapshot })), now);
|
|
278
|
+
const cooling = rows.flatMap((r) => r.cooldowns.map((c) => ({ name: r.name, ...c })));
|
|
279
|
+
if (cooling.length) lines.push('', ...renderCooldowns(cooling, now));
|
|
280
|
+
out(lines.join('\n'));
|
|
275
281
|
}
|
|
276
282
|
|
|
277
283
|
/** settings.json of every pool account, for `setup --apply`. */
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-readable rendering of the account pool: durations, clock times and
|
|
3
|
+
* aligned tables. Pure functions; every time-dependent one takes `now`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const pad2 = (n) => String(n).padStart(2, '0');
|
|
7
|
+
|
|
8
|
+
/** Length of a span of time, e.g. `<1m`, `12m`, `1h02m`, `1d 3h`. Sign is ignored. */
|
|
9
|
+
export function formatDuration(ms) {
|
|
10
|
+
const minutes = Math.floor(Math.abs(ms) / 60_000);
|
|
11
|
+
if (minutes < 1) return '<1m';
|
|
12
|
+
if (minutes < 60) return `${minutes}m`;
|
|
13
|
+
if (minutes < 24 * 60) return `${Math.floor(minutes / 60)}h${pad2(minutes % 60)}m`;
|
|
14
|
+
return `${Math.floor(minutes / 1440)}d ${Math.floor((minutes % 1440) / 60)}h`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** `delta` is target minus now in ms: negative reads "12m ago", otherwise "in 12m". */
|
|
18
|
+
export function formatRelative(delta) {
|
|
19
|
+
const text = formatDuration(delta);
|
|
20
|
+
return delta < 0 ? `${text} ago` : `in ${text}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Local `HH:MM` when `date` is on the same local day as `now`, else `MM-DD HH:MM`. */
|
|
24
|
+
export function formatClock(date, now) {
|
|
25
|
+
const d = new Date(date);
|
|
26
|
+
const n = new Date(now);
|
|
27
|
+
const time = `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
28
|
+
if (d.getFullYear() === n.getFullYear() && d.getMonth() === n.getMonth() && d.getDate() === n.getDate()) return time;
|
|
29
|
+
return `${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${time}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Relative age of an ISO timestamp, or `never` when it is missing. */
|
|
33
|
+
export function formatAgo(iso, now) {
|
|
34
|
+
const t = Date.parse(iso);
|
|
35
|
+
return Number.isFinite(t) ? formatRelative(t - now) : 'never';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const formatPercent = (remaining) => `${Math.round(remaining * 100)}%`;
|
|
39
|
+
|
|
40
|
+
/** `<pct> resets in <rel> (<clock>)`; pct is padded to `pctWidth` (right-aligned). */
|
|
41
|
+
export function formatWindow(window, now, pctWidth = 0) {
|
|
42
|
+
if (!window || typeof window.remaining !== 'number') return '';
|
|
43
|
+
const pct = formatPercent(window.remaining).padStart(pctWidth);
|
|
44
|
+
const reset = Date.parse(window.reset_at);
|
|
45
|
+
if (!Number.isFinite(reset)) return pct;
|
|
46
|
+
if (reset <= now) return `${pct} reset since refresh`;
|
|
47
|
+
return `${pct} resets in ${formatDuration(reset - now)} (${formatClock(reset, now)})`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Pool names in display order: gemini, other, then the rest alphabetically. */
|
|
51
|
+
export function orderedPools(names) {
|
|
52
|
+
const rank = (n) => (n === 'gemini' ? 0 : n === 'other' ? 1 : 2);
|
|
53
|
+
return [...names].sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Plain-text table. Cells are padded to the widest entry of their column and
|
|
58
|
+
* joined with two spaces; lines carry no trailing spaces. A row is an array of
|
|
59
|
+
* cells, or `{ lead: [cells], text }` for a message that runs on after its
|
|
60
|
+
* leading cells (it does not widen any column beyond its lead cells).
|
|
61
|
+
*/
|
|
62
|
+
export function renderTable(headers, rows) {
|
|
63
|
+
const widths = headers.map((h) => h.length);
|
|
64
|
+
for (const row of rows) {
|
|
65
|
+
const cells = Array.isArray(row) ? row : row.lead;
|
|
66
|
+
cells.forEach((c, i) => { widths[i] = Math.max(widths[i], String(c).length); });
|
|
67
|
+
}
|
|
68
|
+
const line = (cells) => cells.map((c, i) => String(c).padEnd(widths[i])).join(' ').trimEnd();
|
|
69
|
+
const lines = [line(headers)];
|
|
70
|
+
for (const row of rows) {
|
|
71
|
+
if (Array.isArray(row)) lines.push(line(row));
|
|
72
|
+
else lines.push(`${row.lead.map((c, i) => String(c).padEnd(widths[i])).join(' ')} ${row.text}`.trimEnd());
|
|
73
|
+
}
|
|
74
|
+
return lines;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const clip = (text, max) => (text.length > max ? `${text.slice(0, max - 3)}...` : text);
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* QUOTA table lines. `entries` is [{ name, snapshot }]. The account name shows
|
|
81
|
+
* on its first row only; AS OF sits on the first pool row.
|
|
82
|
+
*/
|
|
83
|
+
export function renderQuotaTable(entries, now) {
|
|
84
|
+
const windowRows = [];
|
|
85
|
+
const layout = entries.map(({ name, snapshot }) => {
|
|
86
|
+
if (!snapshot) return { name, message: 'not refreshed (run `accounts quota --refresh`)' };
|
|
87
|
+
if (snapshot.error) return { name, message: `error: ${clip(String(snapshot.error).replace(/\s+/g, ' '), 60)}` };
|
|
88
|
+
const pools = orderedPools(Object.keys(snapshot.pools || {}));
|
|
89
|
+
if (!pools.length) return { name, message: 'no quota data' };
|
|
90
|
+
const rows = pools.map((pool) => ({ pool, short: snapshot.pools[pool].short, weekly: snapshot.pools[pool].weekly }));
|
|
91
|
+
windowRows.push(...rows);
|
|
92
|
+
return { name, snapshot, rows };
|
|
93
|
+
});
|
|
94
|
+
const pctWidth = (key) => Math.max(0, ...windowRows.filter((r) => r[key]).map((r) => formatPercent(r[key].remaining).length));
|
|
95
|
+
const shortW = pctWidth('short');
|
|
96
|
+
const weeklyW = pctWidth('weekly');
|
|
97
|
+
const rows = [];
|
|
98
|
+
for (const item of layout) {
|
|
99
|
+
if (item.message) { rows.push({ lead: [item.name], text: item.message }); continue; }
|
|
100
|
+
item.rows.forEach((r, i) => rows.push([
|
|
101
|
+
i === 0 ? item.name : '', r.pool,
|
|
102
|
+
formatWindow(r.short, now, shortW), formatWindow(r.weekly, now, weeklyW),
|
|
103
|
+
i === 0 ? formatAgo(item.snapshot.refreshed_at, now) : '',
|
|
104
|
+
]));
|
|
105
|
+
}
|
|
106
|
+
return renderTable(['QUOTA', 'POOL', '5H', 'WEEKLY', 'AS OF'], rows);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** COOLDOWNS lines (header included), or [] when `cooldowns` is empty. Items: { name, family, retry_at }. */
|
|
110
|
+
export function renderCooldowns(cooldowns, now) {
|
|
111
|
+
if (!cooldowns.length) return [];
|
|
112
|
+
const rows = cooldowns.map((c) => [c.name, c.family,
|
|
113
|
+
`until ${formatClock(c.retry_at, now)} (${formatRelative(Date.parse(c.retry_at) - now)})`]);
|
|
114
|
+
return ['COOLDOWNS', ...renderTable(['', '', ''], rows).slice(1)];
|
|
115
|
+
}
|
package/companion/pool/quota.mjs
CHANGED
|
@@ -131,12 +131,3 @@ export async function refreshQuotas(names, deps) {
|
|
|
131
131
|
});
|
|
132
132
|
return Object.fromEntries(fetched.map(([name, f]) => [name, f.snapshot]));
|
|
133
133
|
}
|
|
134
|
-
|
|
135
|
-
/** One-line quota summary for list/quota output. */
|
|
136
|
-
export function summarizeSnapshot(snapshot) {
|
|
137
|
-
if (!snapshot) return 'quota: not refreshed';
|
|
138
|
-
if (snapshot.error) return `quota: error (${snapshot.error})`;
|
|
139
|
-
const pct = (w) => (w ? `${Math.round(w.remaining * 100)}%` : '?');
|
|
140
|
-
const parts = Object.entries(snapshot.pools).map(([g, w]) => `${g} 5h ${pct(w.short)} weekly ${pct(w.weekly)}`);
|
|
141
|
-
return `quota: ${parts.join(' | ') || 'no data'}${snapshot.plan_type ? ` (${snapshot.plan_type})` : ''}`;
|
|
142
|
-
}
|
package/docs/REFERENCE.md
CHANGED
|
@@ -205,10 +205,10 @@ Every agy process for a pool account runs with its own HOME (and `USERPROFILE` o
|
|
|
205
205
|
|
|
206
206
|
| Command | Effect |
|
|
207
207
|
|---|---|
|
|
208
|
-
| `accounts list [--json]` |
|
|
208
|
+
| `accounts list [--json]` | Aligned plain-text tables. First, name, email, state (`ready`, `busy n`, `cooling`, `needs login`, `disabled`) and last use as a relative time (`9m ago`, `never`), plus a `live` row (`your own agy login, never pooled`). Then a `QUOTA` table with, per account and quota pool (`gemini`, `other`, then any others), the remaining percent and reset time of the 5h and weekly windows (`99% resets in 4h12m (15:23)`; `MM-DD HH:MM` when the reset is not today, `reset since refresh` when the cached reset time has passed) and the age of the snapshot (`AS OF`); accounts without a snapshot show `not refreshed`, failed refreshes `error: ...`. Finally a `COOLDOWNS` section listing each active cooldown's family and `until <time> (in <duration>)`, omitted when there are none. `--json` output is unchanged |
|
|
209
209
|
| `accounts login <name>` | Interactive sign-in for a new account, or a re-login of an existing one. See [Login](#login) |
|
|
210
210
|
| `accounts import <name> --home <dir>` | Copy the token (and `settings.json` when present) from an existing HOME-shaped directory, such as an agy-cli-manager account directory (`~/.agy-cli-manager/accounts/<name>`). Same email and duplicate checks as login. Refuses the live home |
|
|
211
|
-
| `accounts quota [<name>...] [--refresh] [--json]` | Print
|
|
211
|
+
| `accounts quota [<name>...] [--refresh] [--json]` | Print the `QUOTA` table and `COOLDOWNS` section described above for the named accounts (all by default), from cached snapshots. With `--refresh`, query Google's Cloud Code service first. See [Quota snapshots](#quota-snapshots) |
|
|
212
212
|
| `accounts enable <name>` / `accounts disable <name>` | Include or exclude an account from selection. Disabled accounts also never lend |
|
|
213
213
|
| `accounts cooldown clear <name> [--family <f>]` | Drop the recorded cooldown for an account, for one model family or for all |
|
|
214
214
|
| `accounts remove <name> [--yes]` | Without `--yes`, print what would be deleted and exit 0. With `--yes`, delete the account directory, its cooldowns, quota snapshot and conversation bindings, and report how many conversations became unusable. Refused while the account is in a lease or loan |
|
|
@@ -471,7 +471,7 @@ To get upstream back later, run `agy-crew uninstall --all` first.
|
|
|
471
471
|
|
|
472
472
|
## Upgrading
|
|
473
473
|
|
|
474
|
-
Claude Code and Codex cache the plugin under a per-**version** directory (e.g. `cache/agy-crew/agy-crew/0.9.
|
|
474
|
+
Claude Code and Codex cache the plugin under a per-**version** directory (e.g. `cache/agy-crew/agy-crew/0.9.3`) and key "is it current?" on that version string, not on the commit. Bump their manifests and `package.json` together when preparing a release. Pi's Git source instead follows the configured ref; local sources read the checkout directly.
|
|
475
475
|
|
|
476
476
|
- **Terminal command (`agy-crew install`)** — for an npm install, run `npm install -g agy-crew@latest`, then `agy-crew install` to render a fresh bundle and re-link; restart the harness. For a checkout, `git pull` is live at once for the source, and `agy-crew install` refreshes the bundle. See [Install command](#install-command).
|
|
477
477
|
- **Claude Code** — `claude plugin marketplace update agy-crew` refreshes the marketplace clone, then `claude plugin update agy-crew@agy-crew` re-copies it into the cache. `install` is **not** the upgrade command: on an already-installed plugin it answers "already installed" and does nothing, whatever the version. And `update` only moves if the version string changed — on an unchanged version it answers "already at the latest version" and leaves the old commit in place. Force the current commit in with `claude plugin uninstall agy-crew@agy-crew && claude plugin install agy-crew@agy-crew`. Restart Claude Code afterwards either way — skills are registered at session start.
|
package/docs/REFERENCE.zh-CN.md
CHANGED
|
@@ -254,10 +254,10 @@ review --prompt "Review the patch at /tmp/change.patch"
|
|
|
254
254
|
|
|
255
255
|
| 命令 | 作用 |
|
|
256
256
|
| --- | --- |
|
|
257
|
-
| `accounts list [--json]` |
|
|
257
|
+
| `accounts list [--json]` | 以对齐的纯文本表格显示。先是名称、邮箱、状态(`ready`、`busy n`、`cooling`、`needs login`、`disabled`)和相对形式的最近使用时间(`9m ago`、`never`),并附一行 `live`(`your own agy login, never pooled`)。随后是 `QUOTA` 表:按账号和配额池(`gemini`、`other`,其余按字母序)显示 5h 与每周窗口的剩余百分比和重置时间(`99% resets in 4h12m (15:23)`;重置不在今天时为 `MM-DD HH:MM`,缓存的重置时间已过时为 `reset since refresh`),以及快照的时效(`AS OF`);没有快照的账号显示 `not refreshed`,刷新失败的显示 `error: ...`。最后是 `COOLDOWNS` 部分,列出每个生效的冷却的家族和 `until <time> (in <duration>)`,没有冷却时省略。`--json` 输出不变 |
|
|
258
258
|
| `accounts login <name>` | 交互式登录:添加新账号,或为已有账号重新登录。见[登录](#登录) |
|
|
259
259
|
| `accounts import <name> --home <dir>` | 从已有的 HOME 形态目录复制令牌(存在时一并复制 `settings.json`),例如 agy-cli-manager 的账号目录(`~/.agy-cli-manager/accounts/<name>`)。邮箱和重复检查与登录相同;不接受 live 自己的 HOME |
|
|
260
|
-
| `accounts quota [<name>...] [--refresh] [--json]` |
|
|
260
|
+
| `accounts quota [<name>...] [--refresh] [--json]` | 按缓存的快照显示上述 `QUOTA` 表和 `COOLDOWNS` 部分(默认所有账号,也可指定名称)。加上 `--refresh` 会先向 Google 的 Cloud Code 服务查询。见[配额快照](#配额快照) |
|
|
261
261
|
| `accounts enable <name>` / `accounts disable <name>` | 让账号加入或退出自动选择。已停用的账号也不会借出凭据 |
|
|
262
262
|
| `accounts cooldown clear <name> [--family <f>]` | 清除该账号的冷却记录,可只清某个模型家族,不指定则全部清除 |
|
|
263
263
|
| `accounts remove <name> [--yes]` | 不带 `--yes` 时只列出将被删除的内容并以 0 退出。带 `--yes` 才会删除账号目录、冷却记录、配额快照和会话绑定,并报告有多少个会话因此无法继续。账号正处于租约或借用中时会拒绝 |
|
|
@@ -562,7 +562,7 @@ agy-crew 与上游 [agy-staff](https://github.com/keli-wen/agy-staff) 不能安
|
|
|
562
562
|
|
|
563
563
|
## 升级
|
|
564
564
|
|
|
565
|
-
Claude Code 和 Codex 按版本号缓存插件,例如 `cache/agy-crew/agy-crew/0.9.
|
|
565
|
+
Claude Code 和 Codex 按版本号缓存插件,例如 `cache/agy-crew/agy-crew/0.9.3`。缓存是否需要更新取决于版本号,而不是仓库的最新提交。因此,准备发布时需要同步更新两个插件 manifest 和 `package.json` 中的版本。
|
|
566
566
|
|
|
567
567
|
### 终端命令(`agy-crew install`)
|
|
568
568
|
|
|
@@ -27,8 +27,8 @@ node "<skill-dir>/../../companion/agy-companion.mjs" accounts <subcommand> [args
|
|
|
27
27
|
|
|
28
28
|
| Command | Purpose |
|
|
29
29
|
| --- | --- |
|
|
30
|
-
| `accounts list [--json]` |
|
|
31
|
-
| `accounts quota [<name>...] [--refresh] [--json]` |
|
|
30
|
+
| `accounts list [--json]` | Aligned tables: each account with email, state and last use (plus a line for live), then a `QUOTA` table (per pool: remaining percent and reset time for the 5h and weekly windows, and how old the snapshot is), then `COOLDOWNS` when any are active. Start here. |
|
|
31
|
+
| `accounts quota [<name>...] [--refresh] [--json]` | The same `QUOTA` table and `COOLDOWNS` section, from cached snapshots. `--refresh` queries Google's Cloud Code service and updates the snapshots; it is the only subcommand that uses the network. |
|
|
32
32
|
| `accounts import <name> --home <dir>` | Copy the token (and `settings.json`) from an existing HOME-shaped directory, for example an agy-cli-manager account. |
|
|
33
33
|
| `accounts enable <name>` / `accounts disable <name>` | Put an account into or out of automatic selection. |
|
|
34
34
|
| `accounts cooldown clear <name> [--family <f>]` | Drop a recorded quota cooldown. Use it only when the user says the quota is back. |
|
|
@@ -55,11 +55,11 @@ If the user already has accounts elsewhere, offer `accounts import` instead of a
|
|
|
55
55
|
| --- | --- | --- |
|
|
56
56
|
| `ready` | Idle and usable | None. |
|
|
57
57
|
| `busy n` | `n` runs hold it | Normal. A new `auto` run prefers an idle account. |
|
|
58
|
-
| `cooling
|
|
58
|
+
| `cooling` | A model family hit its quota; other families still work. The `COOLDOWNS` section lists each family with `until <time> (in <duration>)` | Wait until then, use another model family or account. Do not clear the cooldown on your own. |
|
|
59
59
|
| `needs login` | agy rejected the credential | Tell the user to run `agy-crew accounts login <name>`. Never retry. |
|
|
60
60
|
| `disabled` | Excluded from selection | `enable` only if the user asks. |
|
|
61
61
|
|
|
62
|
-
Cooldowns are per account and per model family (for example `gemini-3.8-flash`, whatever the effort suffix).
|
|
62
|
+
Cooldowns are per account and per model family (for example `gemini-3.8-flash`, whatever the effort suffix). The `QUOTA` table shows the short-window (5H) and weekly remaining percent and reset time per quota pool, with times in local clock (`HH:MM` for today, otherwise `MM-DD HH:MM`) and `AS OF` giving the snapshot age; they are cached, so `quota --refresh` is needed for current numbers. Refresh only when the user asks about current quota or a run failed with a quota error, not for routine checks.
|
|
63
63
|
|
|
64
64
|
## Choosing an account for a run
|
|
65
65
|
|
package/package.json
CHANGED
|
@@ -27,8 +27,8 @@ node "<skill-dir>/../../companion/agy-companion.mjs" accounts <subcommand> [args
|
|
|
27
27
|
|
|
28
28
|
| Command | Purpose |
|
|
29
29
|
| --- | --- |
|
|
30
|
-
| `accounts list [--json]` |
|
|
31
|
-
| `accounts quota [<name>...] [--refresh] [--json]` |
|
|
30
|
+
| `accounts list [--json]` | Aligned tables: each account with email, state and last use (plus a line for live), then a `QUOTA` table (per pool: remaining percent and reset time for the 5h and weekly windows, and how old the snapshot is), then `COOLDOWNS` when any are active. Start here. |
|
|
31
|
+
| `accounts quota [<name>...] [--refresh] [--json]` | The same `QUOTA` table and `COOLDOWNS` section, from cached snapshots. `--refresh` queries Google's Cloud Code service and updates the snapshots; it is the only subcommand that uses the network. |
|
|
32
32
|
| `accounts import <name> --home <dir>` | Copy the token (and `settings.json`) from an existing HOME-shaped directory, for example an agy-cli-manager account. |
|
|
33
33
|
| `accounts enable <name>` / `accounts disable <name>` | Put an account into or out of automatic selection. |
|
|
34
34
|
| `accounts cooldown clear <name> [--family <f>]` | Drop a recorded quota cooldown. Use it only when the user says the quota is back. |
|
|
@@ -55,11 +55,11 @@ If the user already has accounts elsewhere, offer `accounts import` instead of a
|
|
|
55
55
|
| --- | --- | --- |
|
|
56
56
|
| `ready` | Idle and usable | None. |
|
|
57
57
|
| `busy n` | `n` runs hold it | Normal. A new `auto` run prefers an idle account. |
|
|
58
|
-
| `cooling
|
|
58
|
+
| `cooling` | A model family hit its quota; other families still work. The `COOLDOWNS` section lists each family with `until <time> (in <duration>)` | Wait until then, use another model family or account. Do not clear the cooldown on your own. |
|
|
59
59
|
| `needs login` | agy rejected the credential | Tell the user to run `agy-crew accounts login <name>`. Never retry. |
|
|
60
60
|
| `disabled` | Excluded from selection | `enable` only if the user asks. |
|
|
61
61
|
|
|
62
|
-
Cooldowns are per account and per model family (for example `gemini-3.8-flash`, whatever the effort suffix).
|
|
62
|
+
Cooldowns are per account and per model family (for example `gemini-3.8-flash`, whatever the effort suffix). The `QUOTA` table shows the short-window (5H) and weekly remaining percent and reset time per quota pool, with times in local clock (`HH:MM` for today, otherwise `MM-DD HH:MM`) and `AS OF` giving the snapshot age; they are cached, so `quota --refresh` is needed for current numbers. Refresh only when the user asks about current quota or a run failed with a quota error, not for routine checks.
|
|
63
63
|
|
|
64
64
|
## Choosing an account for a run
|
|
65
65
|
|
package/skills/accounts/SKILL.md
CHANGED
|
@@ -26,8 +26,8 @@ node "<skill-dir>/../../companion/agy-companion.mjs" accounts <subcommand> [args
|
|
|
26
26
|
|
|
27
27
|
| Command | Purpose |
|
|
28
28
|
| --- | --- |
|
|
29
|
-
| `accounts list [--json]` |
|
|
30
|
-
| `accounts quota [<name>...] [--refresh] [--json]` |
|
|
29
|
+
| `accounts list [--json]` | Aligned tables: each account with email, state and last use (plus a line for live), then a `QUOTA` table (per pool: remaining percent and reset time for the 5h and weekly windows, and how old the snapshot is), then `COOLDOWNS` when any are active. Start here. |
|
|
30
|
+
| `accounts quota [<name>...] [--refresh] [--json]` | The same `QUOTA` table and `COOLDOWNS` section, from cached snapshots. `--refresh` queries Google's Cloud Code service and updates the snapshots; it is the only subcommand that uses the network. |
|
|
31
31
|
| `accounts import <name> --home <dir>` | Copy the token (and `settings.json`) from an existing HOME-shaped directory, for example an agy-cli-manager account. |
|
|
32
32
|
| `accounts enable <name>` / `accounts disable <name>` | Put an account into or out of automatic selection. |
|
|
33
33
|
| `accounts cooldown clear <name> [--family <f>]` | Drop a recorded quota cooldown. Use it only when the user says the quota is back. |
|
|
@@ -54,11 +54,11 @@ If the user already has accounts elsewhere, offer `accounts import` instead of a
|
|
|
54
54
|
| --- | --- | --- |
|
|
55
55
|
| `ready` | Idle and usable | None. |
|
|
56
56
|
| `busy n` | `n` runs hold it | Normal. A new `auto` run prefers an idle account. |
|
|
57
|
-
| `cooling
|
|
57
|
+
| `cooling` | A model family hit its quota; other families still work. The `COOLDOWNS` section lists each family with `until <time> (in <duration>)` | Wait until then, use another model family or account. Do not clear the cooldown on your own. |
|
|
58
58
|
| `needs login` | agy rejected the credential | Tell the user to run `agy-crew accounts login <name>`. Never retry. |
|
|
59
59
|
| `disabled` | Excluded from selection | `enable` only if the user asks. |
|
|
60
60
|
|
|
61
|
-
Cooldowns are per account and per model family (for example `gemini-3.8-flash`, whatever the effort suffix).
|
|
61
|
+
Cooldowns are per account and per model family (for example `gemini-3.8-flash`, whatever the effort suffix). The `QUOTA` table shows the short-window (5H) and weekly remaining percent and reset time per quota pool, with times in local clock (`HH:MM` for today, otherwise `MM-DD HH:MM`) and `AS OF` giving the snapshot age; they are cached, so `quota --refresh` is needed for current numbers. Refresh only when the user asks about current quota or a run failed with a quota error, not for routine checks.
|
|
62
62
|
|
|
63
63
|
## Choosing an account for a run
|
|
64
64
|
|