@byokit/openclaw 0.9.0 → 0.10.0
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/CHANGELOG.md +21 -0
- package/README.md +55 -26
- package/SECURITY.md +6 -5
- package/dist/auth-status.d.ts +1 -0
- package/dist/auth-store.d.ts +30 -5
- package/dist/auth-store.js +245 -52
- package/dist/classify.js +2 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +17 -2
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/device.d.ts +1 -1
- package/dist/engine.d.ts +5 -2
- package/dist/engine.js +111 -22
- package/dist/generated/methods.d.ts +7 -1
- package/dist/generated/params.d.ts +20 -0
- package/dist/generated/params.js +1 -1
- package/dist/generated/report.json +5 -5
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/kit.d.ts +1 -0
- package/dist/kit.js +10 -3
- package/dist/link.d.ts +1 -1
- package/dist/link.js +3 -3
- package/dist/members.d.ts +1 -0
- package/dist/members.js +12 -2
- package/dist/pick.d.ts +11 -0
- package/dist/pick.js +145 -0
- package/dist/signin.d.ts +1 -0
- package/dist/signin.js +13 -1
- package/dist/testing/fake-gateway.js +7 -1
- package/dist/types.d.ts +88 -1
- package/dist/words.js +3 -0
- package/dist/words.json +38 -1
- package/engine/package-lock.json +84 -84
- package/engine/package.json +1 -1
- package/engine/patches.json +17 -17
- package/package.json +6 -6
package/dist/pick.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { words } from "./words.js";
|
|
2
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
3
|
+
const finite = (n) => typeof n === 'number' && Number.isFinite(n);
|
|
4
|
+
const reset = (r) => r.resetsAt ?? Infinity;
|
|
5
|
+
const roomWords = (left, span) => words(`room.${span}`, { left: `${Math.round(left)}%` });
|
|
6
|
+
// "3:40pm", or "Fri 3:40pm" when it isn't today (matches @byokit/accounts' clock).
|
|
7
|
+
const clock = (t) => (new Date(t).toDateString() === new Date().toDateString() ? '' : new Date(t).toLocaleDateString('en-US', { weekday: 'short' }) + ' ') +
|
|
8
|
+
new Date(t).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }).toLowerCase();
|
|
9
|
+
// One shared eligibility predicate for direct picks, Auto and every explanation row. Room is read once per row.
|
|
10
|
+
// `bound` (kit only) is the accounts a conversation may use; an account outside it is never a candidate, even when
|
|
11
|
+
// the selection names it, and its row carries `out: 'bound'`. An account the selection names is judged only on
|
|
12
|
+
// `bound` and the demand, so an API-key or resting account it names carries no `billing`/`resting` `out`.
|
|
13
|
+
function considerRows(accounts, room, nowMs, demand = [], models, provider, named, bound) {
|
|
14
|
+
return accounts.map((account) => {
|
|
15
|
+
const r = room(account);
|
|
16
|
+
const age = finite(r.at) ? Math.max(0, nowMs - r.at) : 'unknown';
|
|
17
|
+
const stale = typeof age === 'number' && age > DAY_MS;
|
|
18
|
+
const left = r.left === 'unknown' || !finite(r.left) || stale ? 'unknown' : Math.max(0, Math.min(100, r.left));
|
|
19
|
+
const row = { id: account.id, left, age,
|
|
20
|
+
confidence: stale ? 'stale' : left === 'unknown' || age === 'unknown' ? 'unknown' : 'known', reason: 'no_reading' };
|
|
21
|
+
if (r.left !== 'unknown') {
|
|
22
|
+
row.span = r.span;
|
|
23
|
+
if (finite(r.resetsAt))
|
|
24
|
+
row.resetsAt = r.resetsAt;
|
|
25
|
+
}
|
|
26
|
+
if (bound !== undefined && !bound.includes(account.id))
|
|
27
|
+
row.out = 'bound';
|
|
28
|
+
if (!row.out && account.id !== named) {
|
|
29
|
+
if (account.state === 'resting' && finite(account.until) && account.until > nowMs) {
|
|
30
|
+
row.out = 'resting';
|
|
31
|
+
row.until = account.until;
|
|
32
|
+
}
|
|
33
|
+
else if (account.state !== 'ready' && !(account.state === 'resting' && finite(account.until) && account.until <= nowMs))
|
|
34
|
+
row.out = 'state';
|
|
35
|
+
else if (account.billing !== 'subscription')
|
|
36
|
+
row.out = 'billing';
|
|
37
|
+
}
|
|
38
|
+
if (!row.out && demand.length && models) {
|
|
39
|
+
const available = models(account);
|
|
40
|
+
const missing = demand.find((id) => !available.some((m) => m.id === id && m.available));
|
|
41
|
+
if (missing !== undefined) {
|
|
42
|
+
row.out = 'model';
|
|
43
|
+
row.missing = missing;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!row.out && !demand.length && provider !== undefined && account.provider !== provider && account.id !== named)
|
|
47
|
+
row.out = 'provider';
|
|
48
|
+
if (row.out)
|
|
49
|
+
row.reason = row.out;
|
|
50
|
+
else
|
|
51
|
+
row.tier = left === 'unknown' ? 'unknown' : left > 0 || reset(row) <= nowMs ? 'room' : 'exhausted';
|
|
52
|
+
return { account, row };
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/** Every account's `Considered` row, in list order, with the kit-only `bound` applied. */
|
|
56
|
+
export function consider(accounts, defaults, sel, room, nowMs, models, bound) {
|
|
57
|
+
const demand = [...new Set([...(sel.model ? [sel.model] : []), ...(sel.needs ?? [])])];
|
|
58
|
+
const defaultAccount = accounts.find((a) => a.id === defaults.account);
|
|
59
|
+
const named = nameOf(accounts, defaults, sel, bound);
|
|
60
|
+
const provider = sel.provider ?? defaultAccount?.provider ?? accounts[0]?.provider;
|
|
61
|
+
return considerRows(accounts, (a) => room(a, demand), nowMs, demand, models, provider, named, bound).map((c) => c.row);
|
|
62
|
+
}
|
|
63
|
+
const tierOrder = { room: 0, unknown: 1, exhausted: 2 };
|
|
64
|
+
function compare(a, b) {
|
|
65
|
+
const tier = tierOrder[a.row.tier] - tierOrder[b.row.tier];
|
|
66
|
+
if (tier)
|
|
67
|
+
return tier;
|
|
68
|
+
if (a.row.tier === 'unknown')
|
|
69
|
+
return 0;
|
|
70
|
+
if (a.row.tier === 'room' && a.row.left !== b.row.left)
|
|
71
|
+
return Number(b.row.left) - Number(a.row.left);
|
|
72
|
+
// Avoid Infinity - Infinity (NaN); stable sort preserves list order for ties.
|
|
73
|
+
return reset(a.row) === reset(b.row) ? 0 : reset(a.row) < reset(b.row) ? -1 : 1;
|
|
74
|
+
}
|
|
75
|
+
function rankingReason(winner, next) {
|
|
76
|
+
if (winner.row.tier === 'unknown')
|
|
77
|
+
return 'no_reading';
|
|
78
|
+
if (winner.row.tier === 'exhausted')
|
|
79
|
+
return 'refills_first';
|
|
80
|
+
if (!next || next.row.tier !== 'room' || winner.row.left !== next.row.left)
|
|
81
|
+
return 'most_room';
|
|
82
|
+
return reset(winner.row) < reset(next.row) ? 'earlier_reset' : 'list_order';
|
|
83
|
+
}
|
|
84
|
+
function rank(rows) {
|
|
85
|
+
const ranked = rows.filter((c) => !c.row.out).sort(compare);
|
|
86
|
+
const winner = ranked[0];
|
|
87
|
+
if (!winner)
|
|
88
|
+
return undefined;
|
|
89
|
+
const why = ranked.length === 1 ? 'only' : rankingReason(winner, ranked[1]);
|
|
90
|
+
for (const c of ranked)
|
|
91
|
+
c.row.reason = c === winner ? why : rankingReason(winner, c);
|
|
92
|
+
return { account: winner.account, why, row: winner.row };
|
|
93
|
+
}
|
|
94
|
+
/** Auto's winner only; `candidates` are the eligible accounts. Pure and portable; no timers, reads or mid-run switching. */
|
|
95
|
+
export function chooseAccount(candidates, room, nowMs) {
|
|
96
|
+
const pick = rank(considerRows(candidates, room, nowMs));
|
|
97
|
+
return pick && { account: pick.account, why: pick.why };
|
|
98
|
+
}
|
|
99
|
+
// The `'default'` id is the saved default account when it is ready, a subscription (or a non-custom API choice) and
|
|
100
|
+
// in `bound`; a default outside `bound` is treated as not ready (its row has `out: 'bound'`) and the pick falls through.
|
|
101
|
+
function nameOf(accounts, defaults, sel, bound) {
|
|
102
|
+
const defaultAccount = accounts.find((a) => a.id === defaults.account);
|
|
103
|
+
const usable = defaultAccount?.state === 'ready' &&
|
|
104
|
+
(defaultAccount.billing === 'subscription' || (defaultAccount.billing === 'api' && defaultAccount.provider !== 'custom')) &&
|
|
105
|
+
(bound === undefined || bound.includes(defaultAccount.id));
|
|
106
|
+
return sel.account === 'default' ? (usable ? defaultAccount.id : undefined) : sel.account === 'auto' ? undefined : sel.account;
|
|
107
|
+
}
|
|
108
|
+
/** Resolve once before starting. Without `models`, membership is host-validated and a missing model is ''. */
|
|
109
|
+
export function resolveSelection(accounts, defaults, sel, room, nowMs, models, bound) {
|
|
110
|
+
const demand = [...new Set([...(sel.model ? [sel.model] : []), ...(sel.needs ?? [])])];
|
|
111
|
+
const defaultAccount = accounts.find((a) => a.id === defaults.account);
|
|
112
|
+
const named = nameOf(accounts, defaults, sel, bound);
|
|
113
|
+
const provider = sel.provider ?? defaultAccount?.provider ?? accounts[0]?.provider;
|
|
114
|
+
const rows = considerRows(accounts, (a) => room(a, demand), nowMs, demand, models, provider, named, bound);
|
|
115
|
+
const considered = rows.map((c) => c.row);
|
|
116
|
+
const auto = rank(rows); // also fills explanations for every eligible row, even on explicit selection
|
|
117
|
+
if (named !== undefined) {
|
|
118
|
+
const chosen = rows.find((c) => c.account.id === named);
|
|
119
|
+
if (!chosen)
|
|
120
|
+
return { ok: false, code: 'unknown_account', reason: words('auto.none', { provider: provider ?? '' }), considered };
|
|
121
|
+
if (chosen.row.out)
|
|
122
|
+
return { ok: false, code: 'not_included', reason: words(`pick.out.${chosen.row.out}`, { model: chosen.row.missing ?? '' }), considered };
|
|
123
|
+
return finish(chosen.account, chosen.row, sel.account === 'default' ? 'default' : 'chosen', sel.account === 'default' ? 'default' : 'chosen');
|
|
124
|
+
}
|
|
125
|
+
if (sel.account === 'default' && defaults.auto === false) {
|
|
126
|
+
const first = rows.find((c) => !c.row.out);
|
|
127
|
+
if (first)
|
|
128
|
+
return finish(first.account, first.row, 'default', 'first_ready');
|
|
129
|
+
}
|
|
130
|
+
else if (auto)
|
|
131
|
+
return finish(auto.account, auto.row, 'auto', auto.why);
|
|
132
|
+
return { ok: false, code: 'none', reason: words('auto.none', { provider: provider ?? '' }), considered };
|
|
133
|
+
function finish(account, row, how, why) {
|
|
134
|
+
const available = models?.(account).filter((m) => m.available);
|
|
135
|
+
const model = sel.model ?? (defaults.model && (!available || available.some((m) => m.id === defaults.model)) ? defaults.model : available?.[0]?.id) ?? '';
|
|
136
|
+
if (models && !model)
|
|
137
|
+
return { ok: false, code: 'not_included', reason: words('pick.out.model', { model: sel.model ?? '' }), considered };
|
|
138
|
+
row.reason = why;
|
|
139
|
+
const slots = { name: account.name, provider: account.provider };
|
|
140
|
+
const reason = how !== 'auto' ? words(`pick.why.${why}`, slots) : row.tier === 'unknown' ? words('auto.unknown', slots) : row.tier === 'exhausted' ?
|
|
141
|
+
words(finite(row.resetsAt) ? 'auto.refills' : 'auto.refillsNoTime', { ...slots, time: finite(row.resetsAt) ? clock(row.resetsAt) : '' }) :
|
|
142
|
+
words('auto.room', { ...slots, room: roomWords(Number(row.left), row.span ?? 'tightest') });
|
|
143
|
+
return { ok: true, account, model, how, why, reason, considered };
|
|
144
|
+
}
|
|
145
|
+
}
|
package/dist/signin.d.ts
CHANGED
|
@@ -21,4 +21,5 @@ export declare function signIn(ctx: SignInCtx, member: Member, o: SignInOptions,
|
|
|
21
21
|
};
|
|
22
22
|
/** Usable provider profiles plus native Claude Code readiness reported by the engine. */
|
|
23
23
|
export declare function providers(ctx: SignInCtx, member: Member, refresh?: boolean): Promise<string[]>;
|
|
24
|
+
/** Signs the member's agent out of its own profiles of `provider` only (5.7, 5.15). */
|
|
24
25
|
export declare function signOut(ctx: SignInCtx, member: Member, provider: string): Promise<void>;
|
package/dist/signin.js
CHANGED
|
@@ -433,7 +433,19 @@ export async function providers(ctx, member, refresh) {
|
|
|
433
433
|
return signedInProviders(await (ctx.authStatus ? ctx.authStatus(agentId, refresh ?? false, true)
|
|
434
434
|
: authStatus(ctx.request, agentId, refresh, true))) ?? [];
|
|
435
435
|
}
|
|
436
|
+
/** Signs the member's agent out of its own profiles of `provider` only (5.7, 5.15). */
|
|
436
437
|
export async function signOut(ctx, member, provider) {
|
|
437
438
|
const { agentId } = await ctx.ensure(member);
|
|
438
|
-
|
|
439
|
+
// Without profileIds the pin removes the provider's profiles from every owner store, another member's included.
|
|
440
|
+
// The inherited base is empty (5.15), so every profile this agent's status lists is its own.
|
|
441
|
+
const read = (refresh) => ctx.authStatus ? ctx.authStatus(agentId, refresh, false) : authStatus(ctx.request, agentId, refresh);
|
|
442
|
+
let status = await read(false);
|
|
443
|
+
if (status.unavailable)
|
|
444
|
+
status = await read(true); // no prepared snapshot yet: build it once
|
|
445
|
+
if (status.unavailable)
|
|
446
|
+
throw new Error(String(status.unavailable.message ?? 'account status unavailable'));
|
|
447
|
+
const profileIds = (status.providers ?? []).flatMap((row) => typeof row === 'string' || row.provider !== provider ? []
|
|
448
|
+
: (row.profiles ?? []).map((p) => p.profileId).filter((id) => typeof id === 'string' && id !== ''));
|
|
449
|
+
if (profileIds.length)
|
|
450
|
+
await ctx.request('models.authLogout', { provider, agentId, profileIds }, { timeoutMs: STATUS_MS });
|
|
439
451
|
}
|
|
@@ -222,11 +222,17 @@ export function fakeGateway(script, o) {
|
|
|
222
222
|
ensureAgent(id, workspace);
|
|
223
223
|
return { id };
|
|
224
224
|
},
|
|
225
|
+
// One profile per signed-in provider, `<provider>:default`, as the pin lists an agent's own profiles.
|
|
225
226
|
'models.authStatus': (p) => ({
|
|
226
|
-
providers: (agents.get(String(p?.agentId ?? 'main'))?.providers ?? []).map((provider) => ({ provider
|
|
227
|
+
providers: (agents.get(String(p?.agentId ?? 'main'))?.providers ?? []).map((provider) => ({ provider,
|
|
228
|
+
profiles: [{ profileId: `${provider}:default`, type: 'oauth', status: 'ok' }] })),
|
|
227
229
|
}),
|
|
228
230
|
'models.authLogout': (p) => {
|
|
229
231
|
const agent = agents.get(String(p?.agentId ?? 'main'));
|
|
232
|
+
// The pin refuses a profileIds list naming a profile the agent does not hold.
|
|
233
|
+
if (p.profileIds !== undefined && (!Array.isArray(p.profileIds) || !p.profileIds.length
|
|
234
|
+
|| p.profileIds.some((id) => id !== `${p.provider}:default` || !agent?.providers.includes(String(p.provider)))))
|
|
235
|
+
throw new Error('profileIds contain unavailable auth profiles');
|
|
230
236
|
if (agent)
|
|
231
237
|
agent.providers = agent.providers.filter((provider) => provider !== p.provider);
|
|
232
238
|
if (keys.get(String(p.agentId))?.provider === p.provider)
|
package/dist/types.d.ts
CHANGED
|
@@ -141,8 +141,13 @@ export type Decision = {
|
|
|
141
141
|
};
|
|
142
142
|
export type KitState = {
|
|
143
143
|
phase: 'stopped' | 'installing' | 'starting' | 'repairing' | 'ready' | 'restarting' | 'failed' | 'needs-update' | 'locked';
|
|
144
|
-
why?: 'install' | 'handshake' | 'exited' | 'port' | 'version' | 'engine-already-running' | 'engine-patch' | 'sign-in-reset' | 'auth-store-unreadable';
|
|
144
|
+
why?: 'install' | 'handshake' | 'exited' | 'port' | 'version' | 'engine-already-running' | 'engine-patch' | 'sign-in-reset' | 'auth-store-unreadable' | 'auth-store-seal-size';
|
|
145
145
|
retryAt?: number;
|
|
146
|
+
sealSize?: {
|
|
147
|
+
size: number;
|
|
148
|
+
cap: number;
|
|
149
|
+
file?: string;
|
|
150
|
+
};
|
|
146
151
|
patchSet?: string | null;
|
|
147
152
|
};
|
|
148
153
|
export type Hello = {
|
|
@@ -242,3 +247,85 @@ export type KitEventName = GatewayEventName | 'byokit.browser';
|
|
|
242
247
|
export type KitEventPayload<E extends KitEventName> = E extends 'byokit.browser' ? BrowserPing : GatewayEventPayload<Extract<E, GatewayEventName>>;
|
|
243
248
|
export type { GatewayMethod, GatewayParams, GatewayResult } from './generated/methods.ts';
|
|
244
249
|
export type { GatewayEventName, GatewayEventPayload } from './generated/events.ts';
|
|
250
|
+
export type AccountId = string;
|
|
251
|
+
export type AccountRef = AccountId;
|
|
252
|
+
export type SignInState = 'ready' | 'signing' | 'resting' | 'signed_out' | 'needs_again' | 'not_included';
|
|
253
|
+
export type Account = {
|
|
254
|
+
id: AccountId;
|
|
255
|
+
provider: string;
|
|
256
|
+
route: string;
|
|
257
|
+
name: string;
|
|
258
|
+
label: string;
|
|
259
|
+
email?: string;
|
|
260
|
+
plan?: string;
|
|
261
|
+
billing: 'subscription' | 'api';
|
|
262
|
+
state: SignInState;
|
|
263
|
+
until?: number;
|
|
264
|
+
addedAt: number;
|
|
265
|
+
};
|
|
266
|
+
export type RoomSpan = 'session' | 'week' | 'month' | 'tightest';
|
|
267
|
+
export type Room = {
|
|
268
|
+
left: number;
|
|
269
|
+
span: RoomSpan;
|
|
270
|
+
resetsAt?: number;
|
|
271
|
+
at?: number;
|
|
272
|
+
} | {
|
|
273
|
+
left: 'unknown';
|
|
274
|
+
at?: number;
|
|
275
|
+
};
|
|
276
|
+
export type ModelInfo = {
|
|
277
|
+
id: string;
|
|
278
|
+
name: string;
|
|
279
|
+
tier?: 'strong' | 'fast';
|
|
280
|
+
available: boolean;
|
|
281
|
+
why?: 'plan' | 'resting' | 'signed_out';
|
|
282
|
+
until?: number;
|
|
283
|
+
};
|
|
284
|
+
export type RunSelection = {
|
|
285
|
+
account: AccountRef | 'default' | 'auto';
|
|
286
|
+
model?: string;
|
|
287
|
+
needs?: string[];
|
|
288
|
+
provider?: string;
|
|
289
|
+
};
|
|
290
|
+
export type Defaults = {
|
|
291
|
+
account?: AccountId;
|
|
292
|
+
model?: string;
|
|
293
|
+
auto?: boolean;
|
|
294
|
+
};
|
|
295
|
+
export type Considered = {
|
|
296
|
+
id: AccountId;
|
|
297
|
+
out?: 'state' | 'resting' | 'billing' | 'model' | 'provider' | 'bound';
|
|
298
|
+
until?: number;
|
|
299
|
+
missing?: string;
|
|
300
|
+
tier?: 'room' | 'unknown' | 'exhausted';
|
|
301
|
+
left: number | 'unknown';
|
|
302
|
+
span?: RoomSpan;
|
|
303
|
+
resetsAt?: number;
|
|
304
|
+
age: number | 'unknown';
|
|
305
|
+
confidence: 'known' | 'stale' | 'unknown';
|
|
306
|
+
reason: PickWhy | 'state' | 'resting' | 'billing' | 'model' | 'provider' | 'bound';
|
|
307
|
+
};
|
|
308
|
+
export type PickWhy = 'chosen' | 'default' | 'first_ready' | 'only' | 'most_room' | 'earlier_reset' | 'list_order' | 'no_reading' | 'refills_first';
|
|
309
|
+
export type AccountPick = {
|
|
310
|
+
ok: true;
|
|
311
|
+
account: Account;
|
|
312
|
+
model: string;
|
|
313
|
+
how: 'chosen' | 'default' | 'auto';
|
|
314
|
+
why: PickWhy;
|
|
315
|
+
reason: string;
|
|
316
|
+
considered: Considered[];
|
|
317
|
+
} | {
|
|
318
|
+
ok: false;
|
|
319
|
+
code: 'none' | 'not_included' | 'unknown_account' | 'bound' | 'paid';
|
|
320
|
+
reason: string;
|
|
321
|
+
considered: Considered[];
|
|
322
|
+
};
|
|
323
|
+
export type MoveResult = {
|
|
324
|
+
ok: true;
|
|
325
|
+
session: string;
|
|
326
|
+
} | {
|
|
327
|
+
ok: false;
|
|
328
|
+
code: 'too_early' | 'busy' | 'unsupported' | 'env_mismatch' | 'close_failed' | 'start_failed';
|
|
329
|
+
message: string;
|
|
330
|
+
live?: string;
|
|
331
|
+
};
|
package/dist/words.js
CHANGED
|
@@ -17,12 +17,15 @@ const PHASE_WORDS = {
|
|
|
17
17
|
locked: 'engine.locked',
|
|
18
18
|
'needs-update': 'engine.needsUpdate',
|
|
19
19
|
};
|
|
20
|
+
const MiB = 1024 * 1024;
|
|
20
21
|
export function stateWords(s) {
|
|
21
22
|
// 'stopped' is never on screen (4.3 covers what a person can see), so 5.14 has no row for it.
|
|
22
23
|
if (s.phase === 'failed' && s.why === 'engine-already-running')
|
|
23
24
|
return words('engine.alreadyRunning');
|
|
24
25
|
if (s.why === 'auth-store-unreadable')
|
|
25
26
|
return words('engine.authStoreUnreadable');
|
|
27
|
+
if (s.why === 'auth-store-seal-size' && s.sealSize)
|
|
28
|
+
return words('engine.authStoreSealSize', { store: s.sealSize.file ? words('engine.store.engineData', { file: s.sealSize.file }) : words('engine.store.savedSignIn'), size: String(Math.floor(s.sealSize.size / MiB)), limit: String(s.sealSize.cap / MiB) });
|
|
26
29
|
if (s.why === 'sign-in-reset')
|
|
27
30
|
return words('engine.signInAgain');
|
|
28
31
|
return s.phase === 'stopped' ? '' : words(PHASE_WORDS[s.phase]);
|
package/dist/words.json
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
"engine.repairing": "Fixing a small problem with the setup. This takes a moment.",
|
|
5
5
|
"engine.locked": "Your saved sign-in is locked. Unlock your password storage, then try again.",
|
|
6
6
|
"engine.authStoreUnreadable": "Your saved sign-in couldn't be opened. It is unchanged. Restore its original key or a working backup, then retry.",
|
|
7
|
+
"engine.authStoreSealSize": "{store} is too large to keep safely (more than {size} MB; the limit is {limit} MB). Your sign-ins were kept.",
|
|
8
|
+
"engine.store.engineData": "Engine data {file}",
|
|
9
|
+
"engine.store.savedSignIn": "Your saved sign-in data",
|
|
7
10
|
"engine.signInAgain": "Your saved sign-in couldn't be opened, so it was kept aside. Sign in again.",
|
|
8
11
|
"engine.ready": "Ready.",
|
|
9
12
|
"engine.restarting": "Something stopped. Starting it again by itself.",
|
|
@@ -71,5 +74,39 @@
|
|
|
71
74
|
"key.ok": "Your key is ready. Use it only when you choose this option.",
|
|
72
75
|
"key.invalid": "This key could not be saved. Check it and try again.",
|
|
73
76
|
"key.notIncluded": "This option is not available here.",
|
|
74
|
-
"key.missing": "Add an API key to use this option."
|
|
77
|
+
"key.missing": "Add an API key to use this option.",
|
|
78
|
+
"auto.room": "Right now that's {name}: {room}",
|
|
79
|
+
"auto.unknown": "Right now that's {name} (no recent reading)",
|
|
80
|
+
"auto.refills": "All {provider} accounts are out of room until {time}. {name} refills first.",
|
|
81
|
+
"auto.refillsNoTime": "All {provider} accounts are out of room. {name} refills first.",
|
|
82
|
+
"auto.terms": "Auto may use either of a provider's accounts.",
|
|
83
|
+
"auto.none": "No signed-in {provider} account.",
|
|
84
|
+
"room.unknown": "Room left unknown",
|
|
85
|
+
"room.session": "{left} left this session",
|
|
86
|
+
"room.week": "{left} left this week",
|
|
87
|
+
"room.month": "{left} left this month",
|
|
88
|
+
"room.tightest": "{left} left for now",
|
|
89
|
+
"pick.out.state": "Not signed in right now",
|
|
90
|
+
"pick.out.resting": "Taking a break until {time}",
|
|
91
|
+
"pick.out.billing": "Billed per use, so used only when you choose it",
|
|
92
|
+
"pick.out.model": "Doesn't include {model}",
|
|
93
|
+
"pick.out.provider": "A different service",
|
|
94
|
+
"pick.out.bound": "This conversation uses another account",
|
|
95
|
+
"pick.why.chosen": "You chose {name}.",
|
|
96
|
+
"pick.why.default": "{name} is your default.",
|
|
97
|
+
"pick.why.first_ready": "Your default isn't ready, so {name}, the first ready account.",
|
|
98
|
+
"pick.why.only": "{name} is the only account that can take this.",
|
|
99
|
+
"pick.why.most_room": "{name} has the most room left.",
|
|
100
|
+
"pick.why.earlier_reset": "{name} has as much room left and refills sooner.",
|
|
101
|
+
"pick.why.list_order": "{name} is tied for room and comes first in your list.",
|
|
102
|
+
"pick.why.no_reading": "No account has a recent reading, so {name}, first in your list.",
|
|
103
|
+
"pick.why.refills_first": "All accounts are out of room; {name} refills first.",
|
|
104
|
+
"pick.age": "Read {ago} ago",
|
|
105
|
+
"pick.ageUnknown": "Reading time unknown",
|
|
106
|
+
"ago.minutes": "{n} min",
|
|
107
|
+
"ago.hours": "{n} h",
|
|
108
|
+
"account.bound": "This conversation uses {name}. Move it to switch accounts.",
|
|
109
|
+
"account.paid": "This conversation uses {name}, which is billed per use. Choose {name} to keep going.",
|
|
110
|
+
"account.signOutFirst": "To add a different {provider} account, sign out of {provider} in your browser first.",
|
|
111
|
+
"account.legacyMember": "This person needs a new profile before adding more accounts."
|
|
75
112
|
}
|