@cortexkit/opencode-openai-auth 0.11.1 → 1.0.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/README.md +15 -26
- package/dist/index-766sfrh5.js +1 -0
- package/dist/index-8kj7sa1p.js +12 -0
- package/dist/index-b3q3j39x.js +2 -0
- package/dist/index-bmd7x5aq.js +2 -0
- package/dist/index-eqh53k8v.js +2 -0
- package/dist/{index-sdt2d2ra.js → index-hy9030jh.js} +1 -1
- package/dist/index-jyr8y8k4.js +7 -0
- package/dist/index-rv08jmd4.js +2 -0
- package/dist/index-ww91hyzr.js +221 -0
- package/dist/index.d.ts +578 -145
- package/dist/index.js +1 -110
- package/dist/pool-lifecycle-DbDmgabd.d.ts +2430 -0
- package/dist/port-file-Do3lQIEk.d.ts +29 -0
- package/dist/protocol-BOBdkqqm.d.ts +198 -0
- package/dist/rpc/port-file.d.ts +4 -17
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +2 -2
- package/dist/rpc/protocol.js +1 -0
- package/dist/rpc/rpc-client.d.ts +5 -4
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.d.ts +2 -0
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/sidebar-state-D6cUKgkm.d.ts +385 -0
- package/dist/sidebar-state.d.ts +2 -2
- package/dist/sidebar-state.js +1 -1
- package/dist/token-util-wdza526f.js +1 -0
- package/dist/{token-9823y0pc.js → token-ypk61xj0.js} +1 -1
- package/dist/tui-preferences.js +5 -2
- package/dist/tui.d.ts +9 -4
- package/dist/v2/server.d.ts +54 -0
- package/dist/v2/server.js +4 -0
- package/package.json +21 -9
- package/src/tui/entry.mjs +26 -25
- package/src/tui-compiled/raw/logger.js +24 -0
- package/src/tui-compiled/raw/rpc/protocol.js +3 -0
- package/src/tui-compiled/raw/rpc/rpc-client.js +7 -0
- package/src/tui-compiled/raw/rpc/rpc-dir.js +27 -0
- package/src/tui-compiled/raw/selector.js +21 -0
- package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +138 -0
- package/src/tui-compiled/raw/shared/04a6357627de5ed0-errors.js +38 -0
- package/src/tui-compiled/raw/shared/053cc17b5419261e-codec.js +12 -0
- package/src/tui-compiled/raw/shared/0766d78c6f481f18-watcher.js +157 -0
- package/src/tui-compiled/raw/shared/08eef45626a94992-reserved.js +13 -0
- package/src/tui-compiled/raw/shared/39c3cb05738fcf6d-projection.js +134 -0
- package/src/tui-compiled/raw/shared/3c2d4169d00f907e-tui-support.js +5 -0
- package/src/tui-compiled/raw/shared/3e7d45a77c6ad4d5-index.js +4 -0
- package/src/tui-compiled/raw/shared/415792e85d80819e-capture-sink.js +13 -0
- package/src/tui-compiled/raw/shared/42ae93ab554fcec2-open-url.js +28 -0
- package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
- package/src/tui-compiled/raw/shared/458b934e92490331-mutate.js +500 -0
- package/src/tui-compiled/raw/shared/45bf5343bbbf9add-admission.js +142 -0
- package/src/tui-compiled/raw/shared/46f3dff1266160e8-client.js +2 -0
- package/src/tui-compiled/raw/shared/4825e03209d6ae3e-with-lock.js +48 -0
- package/src/tui-compiled/raw/shared/4f36d333d081d040-merge.js +162 -0
- package/src/tui-compiled/raw/shared/54eba94fd64c8c3c-index.js +7 -0
- package/src/tui-compiled/raw/shared/7211cfec06a298bc-atomic-write.js +80 -0
- package/src/tui-compiled/raw/shared/754d6017f8e419cf-builtins.js +534 -0
- package/src/tui-compiled/raw/shared/75bd8c3bd63b315f-identity.js +79 -0
- package/src/tui-compiled/raw/shared/7a91bb630e6f4804-index.js +1 -0
- package/src/tui-compiled/raw/shared/826342a4b6a8b89b-model.js +16 -0
- package/src/tui-compiled/raw/shared/83068c0dbe8a6f13-publication.js +304 -0
- package/src/tui-compiled/raw/shared/836fc615de6afbd9-settings.js +120 -0
- package/src/tui-compiled/raw/shared/83bc3ba9d0fe2761-runtime.js +68 -0
- package/src/tui-compiled/raw/shared/8a18abe4fa2977c8-menu.js +300 -0
- package/src/tui-compiled/raw/shared/901e149c5a0dbbff-sticky.js +339 -0
- package/src/tui-compiled/raw/shared/9020c407ce64f672-ordered.js +58 -0
- package/src/tui-compiled/raw/shared/905bffc0220f43d0-map.js +105 -0
- package/src/tui-compiled/raw/shared/94820e324f2b4c09-seam.js +301 -0
- package/src/tui-compiled/raw/shared/97484d9bb98c705b-provider-state.js +226 -0
- package/src/tui-compiled/raw/shared/9968e3cade6969a1-index.js +2 -0
- package/src/tui-compiled/raw/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
- package/src/tui-compiled/raw/shared/9d3fc9e37456504e-refresh.js +225 -0
- package/src/tui-compiled/raw/shared/9deae3c4da4fd63b-hooks.js +34 -0
- package/src/tui-compiled/raw/shared/a09b6a533c11858f-tui-preferences.js +77 -0
- package/src/tui-compiled/raw/shared/a62a27a204266acd-attribution.js +60 -0
- package/src/tui-compiled/raw/shared/a910277ac43a8626-pull.js +143 -0
- package/src/tui-compiled/raw/shared/b85c5301fddcb98a-paths.js +5 -0
- package/src/tui-compiled/raw/shared/c14988621dc60a75-port-file.js +217 -0
- package/src/tui-compiled/raw/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
- package/src/tui-compiled/raw/shared/c6b176708c446d6d-pins.js +34 -0
- package/src/tui-compiled/raw/shared/c7d252bedee01600-pi.js +206 -0
- package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +120 -0
- package/src/tui-compiled/raw/shared/cc9e0702a752cea7-index.js +3 -0
- package/src/tui-compiled/raw/shared/cd94316a888b0a8e-schema.js +754 -0
- package/src/tui-compiled/raw/shared/d319517e3ea6ad70-redact.js +83 -0
- package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +5 -0
- package/src/tui-compiled/raw/shared/d669d72c40e9e116-logger.js +19 -0
- package/src/tui-compiled/raw/shared/d7dcba96fea1685c-engine.js +246 -0
- package/src/tui-compiled/raw/shared/dbef697d1df1cd84-index.js +5 -0
- package/src/tui-compiled/raw/shared/e4acc4cb713f300c-protocol.js +3 -0
- package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
- package/src/tui-compiled/raw/shared/ec971142548df90e-index.js +6 -0
- package/src/tui-compiled/raw/shared/f37fcefc7aedbf25-error.js +11 -0
- package/src/tui-compiled/raw/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
- package/src/tui-compiled/raw/shared/fd45e82f2f9c0a0d-torn.js +294 -0
- package/src/tui-compiled/raw/shared/fe02abeadc535f77-rows.js +1258 -0
- package/src/tui-compiled/raw/sidebar-state.js +960 -0
- package/src/tui-compiled/raw/tui/command-dialogs.tsx +424 -0
- package/src/tui-compiled/raw/tui-preferences.js +135 -0
- package/src/tui-compiled/raw/tui.tsx +135 -143
- package/src/tui-compiled/runtime/logger.js +24 -0
- package/src/tui-compiled/runtime/rpc/protocol.js +3 -0
- package/src/tui-compiled/runtime/rpc/rpc-client.js +7 -0
- package/src/tui-compiled/runtime/rpc/rpc-dir.js +27 -0
- package/src/tui-compiled/runtime/selector.js +21 -0
- package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +138 -0
- package/src/tui-compiled/runtime/shared/04a6357627de5ed0-errors.js +38 -0
- package/src/tui-compiled/runtime/shared/053cc17b5419261e-codec.js +12 -0
- package/src/tui-compiled/runtime/shared/0766d78c6f481f18-watcher.js +157 -0
- package/src/tui-compiled/runtime/shared/08eef45626a94992-reserved.js +13 -0
- package/src/tui-compiled/runtime/shared/39c3cb05738fcf6d-projection.js +134 -0
- package/src/tui-compiled/runtime/shared/3c2d4169d00f907e-tui-support.js +5 -0
- package/src/tui-compiled/runtime/shared/3e7d45a77c6ad4d5-index.js +4 -0
- package/src/tui-compiled/runtime/shared/415792e85d80819e-capture-sink.js +13 -0
- package/src/tui-compiled/runtime/shared/42ae93ab554fcec2-open-url.js +28 -0
- package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
- package/src/tui-compiled/runtime/shared/458b934e92490331-mutate.js +500 -0
- package/src/tui-compiled/runtime/shared/45bf5343bbbf9add-admission.js +142 -0
- package/src/tui-compiled/runtime/shared/46f3dff1266160e8-client.js +2 -0
- package/src/tui-compiled/runtime/shared/4825e03209d6ae3e-with-lock.js +48 -0
- package/src/tui-compiled/runtime/shared/4f36d333d081d040-merge.js +162 -0
- package/src/tui-compiled/runtime/shared/54eba94fd64c8c3c-index.js +7 -0
- package/src/tui-compiled/runtime/shared/7211cfec06a298bc-atomic-write.js +80 -0
- package/src/tui-compiled/runtime/shared/754d6017f8e419cf-builtins.js +534 -0
- package/src/tui-compiled/runtime/shared/75bd8c3bd63b315f-identity.js +79 -0
- package/src/tui-compiled/runtime/shared/7a91bb630e6f4804-index.js +1 -0
- package/src/tui-compiled/runtime/shared/826342a4b6a8b89b-model.js +16 -0
- package/src/tui-compiled/runtime/shared/83068c0dbe8a6f13-publication.js +304 -0
- package/src/tui-compiled/runtime/shared/836fc615de6afbd9-settings.js +120 -0
- package/src/tui-compiled/runtime/shared/83bc3ba9d0fe2761-runtime.js +68 -0
- package/src/tui-compiled/runtime/shared/8a18abe4fa2977c8-menu.js +300 -0
- package/src/tui-compiled/runtime/shared/901e149c5a0dbbff-sticky.js +339 -0
- package/src/tui-compiled/runtime/shared/9020c407ce64f672-ordered.js +58 -0
- package/src/tui-compiled/runtime/shared/905bffc0220f43d0-map.js +105 -0
- package/src/tui-compiled/runtime/shared/94820e324f2b4c09-seam.js +301 -0
- package/src/tui-compiled/runtime/shared/97484d9bb98c705b-provider-state.js +226 -0
- package/src/tui-compiled/runtime/shared/9968e3cade6969a1-index.js +2 -0
- package/src/tui-compiled/runtime/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
- package/src/tui-compiled/runtime/shared/9d3fc9e37456504e-refresh.js +225 -0
- package/src/tui-compiled/runtime/shared/9deae3c4da4fd63b-hooks.js +34 -0
- package/src/tui-compiled/runtime/shared/a09b6a533c11858f-tui-preferences.js +77 -0
- package/src/tui-compiled/runtime/shared/a62a27a204266acd-attribution.js +60 -0
- package/src/tui-compiled/runtime/shared/a910277ac43a8626-pull.js +143 -0
- package/src/tui-compiled/runtime/shared/b85c5301fddcb98a-paths.js +5 -0
- package/src/tui-compiled/runtime/shared/c14988621dc60a75-port-file.js +217 -0
- package/src/tui-compiled/runtime/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
- package/src/tui-compiled/runtime/shared/c6b176708c446d6d-pins.js +34 -0
- package/src/tui-compiled/runtime/shared/c7d252bedee01600-pi.js +206 -0
- package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +120 -0
- package/src/tui-compiled/runtime/shared/cc9e0702a752cea7-index.js +3 -0
- package/src/tui-compiled/runtime/shared/cd94316a888b0a8e-schema.js +754 -0
- package/src/tui-compiled/runtime/shared/d319517e3ea6ad70-redact.js +83 -0
- package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +5 -0
- package/src/tui-compiled/runtime/shared/d669d72c40e9e116-logger.js +19 -0
- package/src/tui-compiled/runtime/shared/d7dcba96fea1685c-engine.js +246 -0
- package/src/tui-compiled/runtime/shared/dbef697d1df1cd84-index.js +5 -0
- package/src/tui-compiled/runtime/shared/e4acc4cb713f300c-protocol.js +3 -0
- package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
- package/src/tui-compiled/runtime/shared/ec971142548df90e-index.js +6 -0
- package/src/tui-compiled/runtime/shared/f37fcefc7aedbf25-error.js +11 -0
- package/src/tui-compiled/runtime/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
- package/src/tui-compiled/runtime/shared/fd45e82f2f9c0a0d-torn.js +294 -0
- package/src/tui-compiled/runtime/shared/fe02abeadc535f77-rows.js +1258 -0
- package/src/tui-compiled/runtime/sidebar-state.js +960 -0
- package/src/tui-compiled/runtime/tui/command-dialogs.js +378 -0
- package/src/tui-compiled/runtime/tui-preferences.js +135 -0
- package/src/tui-compiled/runtime/{tui.tsx → tui.js} +288 -316
- package/dist/index-4effjmr6.js +0 -2
- package/dist/index-9ehhb051.js +0 -2
- package/dist/index-g3hsdjh4.js +0 -2
- package/dist/index-hh52v8sb.js +0 -2
- package/dist/index-jwn4r3sw.js +0 -1
- package/dist/index-sq36b120.js +0 -211
- package/dist/internal-BTndPmW2.d.ts +0 -914
- package/dist/quota-normalize-08hpp51v.js +0 -1
- package/dist/sidebar-state-CoKYKcLn.d.ts +0 -220
- package/dist/token-util-ypgz7rge.js +0 -1
- package/src/tui-compiled/raw/core-account-paths.ts +0 -114
- package/src/tui-compiled/raw/core-custody-state.ts +0 -243
- package/src/tui-compiled/raw/logger.ts +0 -53
- package/src/tui-compiled/raw/rpc-port-file.ts +0 -177
- package/src/tui-compiled/raw/rpc-protocol.ts +0 -12
- package/src/tui-compiled/raw/rpc-rpc-client.ts +0 -82
- package/src/tui-compiled/raw/rpc-rpc-dir.ts +0 -46
- package/src/tui-compiled/raw/sidebar-state.ts +0 -1659
- package/src/tui-compiled/raw/tui-command-dialogs.tsx +0 -1096
- package/src/tui-compiled/raw/tui-preferences.ts +0 -328
- package/src/tui-compiled/runtime/core-account-paths.ts +0 -114
- package/src/tui-compiled/runtime/core-custody-state.ts +0 -243
- package/src/tui-compiled/runtime/logger.ts +0 -53
- package/src/tui-compiled/runtime/rpc-port-file.ts +0 -177
- package/src/tui-compiled/runtime/rpc-protocol.ts +0 -12
- package/src/tui-compiled/runtime/rpc-rpc-client.ts +0 -82
- package/src/tui-compiled/runtime/rpc-rpc-dir.ts +0 -46
- package/src/tui-compiled/runtime/sidebar-state.ts +0 -1659
- package/src/tui-compiled/runtime/tui-command-dialogs.tsx +0 -855
- package/src/tui-compiled/runtime/tui-preferences.ts +0 -328
- package/src/tui-compiled/shared/internal.ts +0 -6
- package/src/tui-compiled/shared/logger.ts +0 -264
- package/src/tui-compiled/shared/paths.ts +0 -33
- package/src/tui-compiled/shared/refresh-file-lock.ts +0 -384
- package/src/tui-compiled/shared/util/error.ts +0 -9
- package/src/tui-compiled/shared/util/open-url.ts +0 -30
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
// The four sections every plugin gets from the library: Accounts, Quota,
|
|
2
|
+
// Routing and Limits. They read the pool and the plugin's settings through
|
|
3
|
+
// the store and write only through store operations, so a menu action takes
|
|
4
|
+
// the same locks as any other writer of those files.
|
|
5
|
+
import { formatQuota, isQuotaMap, projectQuota, quotaWindowName, } from "./d5a8f1061ee2f114-index.js";
|
|
6
|
+
import { DEFAULT_FORMER_MAIN_ID, orderForPlacement, resolveRoutingMode, } from "./3e7d45a77c6ad4d5-index.js";
|
|
7
|
+
import { projectFailure, } from "./94820e324f2b4c09-seam.js";
|
|
8
|
+
/** `disabledReason` recorded when the user disables an account from the menu. */
|
|
9
|
+
export const MENU_DISABLED_REASON = 'disabled from the command menu';
|
|
10
|
+
function isRecord(value) {
|
|
11
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
function record(value) {
|
|
14
|
+
return isRecord(value) ? { ...value } : {};
|
|
15
|
+
}
|
|
16
|
+
/** The rows the menu can name: one per id, in roster order. */
|
|
17
|
+
function distinctRows(load) {
|
|
18
|
+
if (load.status !== 'ready')
|
|
19
|
+
return [];
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
return load.rows.filter((row) => {
|
|
22
|
+
if (seen.has(row.id))
|
|
23
|
+
return false;
|
|
24
|
+
seen.add(row.id);
|
|
25
|
+
return true;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function rowName(row, identity) {
|
|
29
|
+
return row.label ?? identity ?? row.id;
|
|
30
|
+
}
|
|
31
|
+
function projected(row, scope) {
|
|
32
|
+
return isQuotaMap(row.quota) ? projectQuota(row.quota, scope) : undefined;
|
|
33
|
+
}
|
|
34
|
+
/** Real plurals for counts in summaries: `1 account`, `3 accounts`. */
|
|
35
|
+
function count(n, noun) {
|
|
36
|
+
return `${n} ${noun}${n === 1 ? '' : 's'}`;
|
|
37
|
+
}
|
|
38
|
+
/** The header every section's own actions are listed under. */
|
|
39
|
+
const ACTIONS_GROUP = 'Actions';
|
|
40
|
+
function loadProblem(load) {
|
|
41
|
+
if (load.status === 'pending-migration')
|
|
42
|
+
return 'The account pool has not been migrated yet.';
|
|
43
|
+
if (load.status === 'error')
|
|
44
|
+
return `The ${load.file} file cannot be read: ${load.reason}`;
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
function addedText(name, result) {
|
|
48
|
+
switch (result.outcome) {
|
|
49
|
+
case 'added':
|
|
50
|
+
return `Added ${name}.`;
|
|
51
|
+
case 'added-disabled':
|
|
52
|
+
return `Added ${name}, disabled: another enabled account has the same identity.`;
|
|
53
|
+
case 'completed':
|
|
54
|
+
return `Finished adding ${name}.`;
|
|
55
|
+
case 'rotated':
|
|
56
|
+
return `${name} was already in the pool; its credential was updated.`;
|
|
57
|
+
case 'exists':
|
|
58
|
+
return `${name} was already in the pool; its credential is unchanged.`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* What the user is told about a failed late login: the projected message,
|
|
63
|
+
* never the exception's own text, which can quote the login's request.
|
|
64
|
+
*/
|
|
65
|
+
function failureMessage(error) {
|
|
66
|
+
return projectFailure(error).text;
|
|
67
|
+
}
|
|
68
|
+
async function snapshot(store) {
|
|
69
|
+
const load = await store.read();
|
|
70
|
+
const read = await store.readSettings();
|
|
71
|
+
return {
|
|
72
|
+
load,
|
|
73
|
+
rows: distinctRows(load),
|
|
74
|
+
settings: read.status === 'error' ? {} : read.settings,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function accountView(row, options) {
|
|
78
|
+
const identity = options.accounts?.describeIdentity
|
|
79
|
+
? options.accounts.describeIdentity(row)
|
|
80
|
+
: row.identity;
|
|
81
|
+
// Copy only the fields the menu shows: the store row also carries its
|
|
82
|
+
// credential and fingerprint, which must never reach a payload.
|
|
83
|
+
const account = {
|
|
84
|
+
id: row.id,
|
|
85
|
+
...(row.label !== undefined ? { label: row.label } : {}),
|
|
86
|
+
enabled: row.enabled,
|
|
87
|
+
type: row.type,
|
|
88
|
+
...(identity !== undefined ? { identity } : {}),
|
|
89
|
+
};
|
|
90
|
+
return { account, name: rowName(row, identity) };
|
|
91
|
+
}
|
|
92
|
+
function accountsSection(options, snap) {
|
|
93
|
+
const { store, extraLocks } = options;
|
|
94
|
+
const locks = extraLocks ? { extraLocks } : {};
|
|
95
|
+
const ids = snap.rows.map((row) => row.id);
|
|
96
|
+
const problem = loadProblem(snap.load);
|
|
97
|
+
const enabledCount = snap.rows.filter((row) => row.enabled).length;
|
|
98
|
+
const items = snap.rows.map((row, index) => {
|
|
99
|
+
const { account, name } = accountView(row, options);
|
|
100
|
+
// An Accounts row shows only the credential type, identity and any
|
|
101
|
+
// disabled reason; the account's quota windows are shown in the Quota
|
|
102
|
+
// section instead, so this row fits one line.
|
|
103
|
+
const detail = [
|
|
104
|
+
row.type === 'api' ? 'API key' : 'OAuth',
|
|
105
|
+
...(account.identity !== undefined ? [account.identity] : []),
|
|
106
|
+
...(!row.invalid && !row.enabled && row.disabledReason
|
|
107
|
+
? [`disabled: ${row.disabledReason}`]
|
|
108
|
+
: []),
|
|
109
|
+
].join(' · ');
|
|
110
|
+
const status = row.invalid
|
|
111
|
+
? 'invalid'
|
|
112
|
+
: row.enabled
|
|
113
|
+
? 'enabled'
|
|
114
|
+
: 'disabled';
|
|
115
|
+
const actions = [];
|
|
116
|
+
if (!row.invalid && row.enabled)
|
|
117
|
+
actions.push({
|
|
118
|
+
id: 'disable',
|
|
119
|
+
label: 'Disable',
|
|
120
|
+
run: async () => {
|
|
121
|
+
await store.disable(row.id, MENU_DISABLED_REASON, locks);
|
|
122
|
+
return `Disabled ${name}.`;
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
if (!row.invalid && !row.enabled)
|
|
126
|
+
actions.push({
|
|
127
|
+
id: 'enable',
|
|
128
|
+
label: 'Enable',
|
|
129
|
+
run: async () => {
|
|
130
|
+
await store.enable(row.id, locks);
|
|
131
|
+
return `Enabled ${name}.`;
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
if (ids.length > 1)
|
|
135
|
+
actions.push({
|
|
136
|
+
id: 'move',
|
|
137
|
+
label: 'Move',
|
|
138
|
+
knobs: [
|
|
139
|
+
{
|
|
140
|
+
kind: 'choice',
|
|
141
|
+
id: 'position',
|
|
142
|
+
label: 'Position',
|
|
143
|
+
choices: ids.map((_, at) => ({
|
|
144
|
+
value: String(at + 1),
|
|
145
|
+
label: String(at + 1),
|
|
146
|
+
})),
|
|
147
|
+
value: String(index + 1),
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
run: async ({ values }) => {
|
|
151
|
+
const position = Number(values.position);
|
|
152
|
+
const order = ids.filter((id) => id !== row.id);
|
|
153
|
+
order.splice(position - 1, 0, row.id);
|
|
154
|
+
const result = await store.reorder(order, locks);
|
|
155
|
+
return result.outcome === 'unchanged'
|
|
156
|
+
? `${name} is already at position ${position}.`
|
|
157
|
+
: `Moved ${name} to position ${position}.`;
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
actions.push({
|
|
161
|
+
id: 'remove',
|
|
162
|
+
label: 'Remove',
|
|
163
|
+
irreversible: true,
|
|
164
|
+
confirm: `Remove ${name}? Its stored credential is deleted.`,
|
|
165
|
+
run: async () => {
|
|
166
|
+
await store.remove(row.id, {
|
|
167
|
+
...locks,
|
|
168
|
+
...(options.accounts?.protect
|
|
169
|
+
? { protect: options.accounts.protect }
|
|
170
|
+
: {}),
|
|
171
|
+
});
|
|
172
|
+
return `Removed ${name}.`;
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
return {
|
|
176
|
+
id: row.id,
|
|
177
|
+
label: name,
|
|
178
|
+
detail,
|
|
179
|
+
group: 'Accounts',
|
|
180
|
+
status,
|
|
181
|
+
account,
|
|
182
|
+
actions,
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
const login = options.accounts?.login;
|
|
186
|
+
const actions = [];
|
|
187
|
+
if (login && !problem)
|
|
188
|
+
actions.push({
|
|
189
|
+
id: 'add',
|
|
190
|
+
label: login.label ?? 'Add account',
|
|
191
|
+
group: ACTIONS_GROUP,
|
|
192
|
+
knobs: login.knobs ?? [],
|
|
193
|
+
run: async ({ values, invocation }) => {
|
|
194
|
+
const outcome = await login.run(values, invocation);
|
|
195
|
+
if (outcome.status === 'cancelled')
|
|
196
|
+
return { ok: false, text: outcome.message ?? 'Login cancelled.' };
|
|
197
|
+
if (outcome.status === 'ready') {
|
|
198
|
+
const result = await store.add(outcome.account, locks);
|
|
199
|
+
return addedText(outcome.account.label ?? result.id, result);
|
|
200
|
+
}
|
|
201
|
+
// The login finishes after this call returns: add the account then,
|
|
202
|
+
// and send the outcome to the invocation that started the login.
|
|
203
|
+
void outcome.completion
|
|
204
|
+
.then(async (account) => {
|
|
205
|
+
if (!account) {
|
|
206
|
+
invocation.notify('Login cancelled.', 'warning');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const result = await store.add(account, locks);
|
|
210
|
+
invocation.notify(addedText(account.label ?? result.id, result));
|
|
211
|
+
}, (error) => {
|
|
212
|
+
invocation.notify(`Adding the account failed: ${failureMessage(error)}`, 'error');
|
|
213
|
+
})
|
|
214
|
+
.catch((error) => {
|
|
215
|
+
invocation.notify(`Adding the account failed: ${failureMessage(error)}`, 'error');
|
|
216
|
+
});
|
|
217
|
+
return outcome.message;
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
id: 'accounts',
|
|
222
|
+
slot: 'accounts',
|
|
223
|
+
title: 'Accounts',
|
|
224
|
+
content: {
|
|
225
|
+
lines: problem
|
|
226
|
+
? [problem]
|
|
227
|
+
: [
|
|
228
|
+
snap.rows.length === 0
|
|
229
|
+
? 'No accounts yet'
|
|
230
|
+
: `${count(snap.rows.length, 'account')}, ${enabledCount} enabled`,
|
|
231
|
+
],
|
|
232
|
+
items: problem ? [] : items,
|
|
233
|
+
actions,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function quotaSection(options, snap) {
|
|
238
|
+
const scope = options.quota?.scope ?? 'all';
|
|
239
|
+
const now = options.now();
|
|
240
|
+
// A scoped menu names its model family in the header, since the quota
|
|
241
|
+
// shown is that family's view of each account.
|
|
242
|
+
const group = scope === 'all' ? 'Accounts' : `Accounts · ${scope}`;
|
|
243
|
+
// Checking quota asks the provider for a fresh reading. A plugin that
|
|
244
|
+
// polls its accounts itself (its own HTTP quota call) supplies
|
|
245
|
+
// `quota.check`; otherwise each row's reading is requested from the store,
|
|
246
|
+
// which runs the plugin's pull for that row. Checking one account and
|
|
247
|
+
// checking all use the same path.
|
|
248
|
+
const check = async (ids, invocation) => {
|
|
249
|
+
if (options.quota?.check)
|
|
250
|
+
await options.quota.check(ids, invocation);
|
|
251
|
+
else {
|
|
252
|
+
for (const id of ids)
|
|
253
|
+
options.store.requestReading(id);
|
|
254
|
+
await options.store.pullsSettled();
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
// Only a routing candidate (enabled, valid and holding a credential) is a
|
|
258
|
+
// row, because a row must do something when chosen and its action checks
|
|
259
|
+
// that account's quota. Any other account becomes a read-only line naming
|
|
260
|
+
// why it can't be checked (disabled, invalid, no credential), followed by
|
|
261
|
+
// its last stored quota reading.
|
|
262
|
+
const candidates = snap.rows.filter((row) => row.candidate);
|
|
263
|
+
const items = candidates.map((row) => {
|
|
264
|
+
const { account, name } = accountView(row, options);
|
|
265
|
+
const quota = projected(row, scope);
|
|
266
|
+
return {
|
|
267
|
+
id: row.id,
|
|
268
|
+
label: name,
|
|
269
|
+
group,
|
|
270
|
+
status: formatQuota(quota, { now, form: 'compact' }),
|
|
271
|
+
detail: formatQuota(quota, { now, form: 'full' }),
|
|
272
|
+
account,
|
|
273
|
+
actions: [
|
|
274
|
+
{
|
|
275
|
+
id: 'check',
|
|
276
|
+
label: 'Check this account',
|
|
277
|
+
run: async ({ invocation }) => {
|
|
278
|
+
await check([row.id], invocation);
|
|
279
|
+
return `Checked quota for ${name}.`;
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
};
|
|
284
|
+
});
|
|
285
|
+
const unchecked = snap.rows
|
|
286
|
+
.filter((row) => !row.candidate)
|
|
287
|
+
.map((row) => {
|
|
288
|
+
const { name } = accountView(row, options);
|
|
289
|
+
const reason = row.invalid
|
|
290
|
+
? 'invalid'
|
|
291
|
+
: !row.enabled
|
|
292
|
+
? 'disabled'
|
|
293
|
+
: row.credential === undefined
|
|
294
|
+
? 'no credential'
|
|
295
|
+
: 'unavailable';
|
|
296
|
+
const quota = formatQuota(projected(row, scope), {
|
|
297
|
+
now,
|
|
298
|
+
form: 'compact',
|
|
299
|
+
});
|
|
300
|
+
return `${name} (${reason}): ${quota}`;
|
|
301
|
+
});
|
|
302
|
+
const actions = [];
|
|
303
|
+
if (candidates.length > 0)
|
|
304
|
+
actions.push({
|
|
305
|
+
id: 'check',
|
|
306
|
+
label: 'Check now',
|
|
307
|
+
description: 'Checks every account that can be checked.',
|
|
308
|
+
group: ACTIONS_GROUP,
|
|
309
|
+
run: async ({ invocation }) => {
|
|
310
|
+
const ids = candidates.map((row) => row.id);
|
|
311
|
+
await check(ids, invocation);
|
|
312
|
+
return `Checked quota for ${count(ids.length, 'account')}.`;
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
return {
|
|
316
|
+
id: 'quota',
|
|
317
|
+
slot: 'quota',
|
|
318
|
+
title: 'Quota',
|
|
319
|
+
content: {
|
|
320
|
+
lines: snap.rows.length === 0 ? ['No accounts yet'] : unchecked,
|
|
321
|
+
items,
|
|
322
|
+
actions,
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function routingSection(options, snap) {
|
|
327
|
+
const { store, extraLocks } = options;
|
|
328
|
+
const locks = extraLocks ? { extraLocks } : {};
|
|
329
|
+
const choices = [
|
|
330
|
+
{ value: 'ordered', label: 'Ordered (roster order)' },
|
|
331
|
+
...(options.routing?.orderedVariants ?? []),
|
|
332
|
+
{ value: 'sticky-balanced', label: 'Sticky balanced' },
|
|
333
|
+
];
|
|
334
|
+
const raw = record(snap.settings.routing).mode;
|
|
335
|
+
const current = typeof raw === 'string' && choices.some((choice) => choice.value === raw)
|
|
336
|
+
? raw
|
|
337
|
+
: 'ordered';
|
|
338
|
+
const resolved = resolveRoutingMode(current);
|
|
339
|
+
const ids = snap.rows.map((row) => row.id);
|
|
340
|
+
const tried = orderForPlacement(ids, resolved.placement, options.routing?.formerMainId ?? DEFAULT_FORMER_MAIN_ID);
|
|
341
|
+
const label = choices.find((choice) => choice.value === current)?.label ?? current;
|
|
342
|
+
const lines = [`Mode: ${label}`];
|
|
343
|
+
if (ids.length > 0)
|
|
344
|
+
lines.push(`Roster order: ${ids.join(', ')}`);
|
|
345
|
+
if (resolved.mode === 'ordered' && tried.join() !== ids.join())
|
|
346
|
+
lines.push(`Tried in order: ${tried.join(', ')}`);
|
|
347
|
+
const actions = [
|
|
348
|
+
{
|
|
349
|
+
id: 'mode',
|
|
350
|
+
label: 'Change mode',
|
|
351
|
+
group: ACTIONS_GROUP,
|
|
352
|
+
knobs: [
|
|
353
|
+
{ kind: 'choice', id: 'mode', label: 'Mode', choices, value: current },
|
|
354
|
+
],
|
|
355
|
+
run: async ({ values }) => {
|
|
356
|
+
const mode = String(values.mode);
|
|
357
|
+
await store.updateSettings((settings) => {
|
|
358
|
+
settings.routing = { ...record(settings.routing), mode };
|
|
359
|
+
return undefined;
|
|
360
|
+
}, locks);
|
|
361
|
+
return `Routing mode set to ${choices.find((choice) => choice.value === mode)?.label ?? mode}.`;
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
];
|
|
365
|
+
if (ids.length > 1)
|
|
366
|
+
actions.push({
|
|
367
|
+
id: 'order',
|
|
368
|
+
label: 'Set order',
|
|
369
|
+
group: ACTIONS_GROUP,
|
|
370
|
+
knobs: [
|
|
371
|
+
{
|
|
372
|
+
kind: 'text',
|
|
373
|
+
id: 'order',
|
|
374
|
+
label: 'Account ids, first to last',
|
|
375
|
+
value: ids.join(', '),
|
|
376
|
+
required: true,
|
|
377
|
+
},
|
|
378
|
+
],
|
|
379
|
+
run: async ({ values }) => {
|
|
380
|
+
const order = String(values.order)
|
|
381
|
+
.split(/[\s,]+/)
|
|
382
|
+
.filter((id) => id.length > 0);
|
|
383
|
+
const result = await store.reorder(order, locks);
|
|
384
|
+
return result.outcome === 'unchanged'
|
|
385
|
+
? 'The order is unchanged.'
|
|
386
|
+
: `Order set to ${result.ids.join(', ')}.`;
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
return {
|
|
390
|
+
id: 'routing',
|
|
391
|
+
slot: 'routing',
|
|
392
|
+
title: 'Routing',
|
|
393
|
+
content: { lines, actions },
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function floorLabels(options, snap) {
|
|
397
|
+
if (options.limits?.labels)
|
|
398
|
+
return [...options.limits.labels];
|
|
399
|
+
const scope = options.quota?.scope ?? 'all';
|
|
400
|
+
const labels = [];
|
|
401
|
+
for (const row of snap.rows)
|
|
402
|
+
for (const limit of projected(row, scope)?.limits ?? [])
|
|
403
|
+
if (!labels.includes(limit.label))
|
|
404
|
+
labels.push(limit.label);
|
|
405
|
+
return labels.length > 0 ? labels : ['primary'];
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* The display name for each floor. A floor is keyed by the quota window
|
|
409
|
+
* label the plugin stores readings under (`primary`, `five_hour`). When some
|
|
410
|
+
* account's stored quota reading records that window's length, the floor is
|
|
411
|
+
* shown under the same name `formatQuota` gives the window (`5h`, `7d`);
|
|
412
|
+
* otherwise under the label itself. The settings file keeps the label.
|
|
413
|
+
*/
|
|
414
|
+
function floorNames(options, snap) {
|
|
415
|
+
const scope = options.quota?.scope ?? 'all';
|
|
416
|
+
const names = new Map();
|
|
417
|
+
for (const row of snap.rows)
|
|
418
|
+
for (const limit of projected(row, scope)?.limits ?? [])
|
|
419
|
+
if (limit.windowMinutes !== undefined && !names.has(limit.label))
|
|
420
|
+
names.set(limit.label, quotaWindowName(limit));
|
|
421
|
+
return (label) => names.get(label) ?? label;
|
|
422
|
+
}
|
|
423
|
+
function limitsSection(options, snap) {
|
|
424
|
+
const { store, extraLocks } = options;
|
|
425
|
+
const locks = extraLocks ? { extraLocks } : {};
|
|
426
|
+
const killswitch = record(snap.settings.killswitch);
|
|
427
|
+
const enabled = killswitch.enabled === true;
|
|
428
|
+
const floors = record(killswitch.accounts);
|
|
429
|
+
const labels = floorLabels(options, snap);
|
|
430
|
+
const nameOf = floorNames(options, snap);
|
|
431
|
+
const state = enabled ? 'on' : 'off';
|
|
432
|
+
const items = snap.rows.map((row) => {
|
|
433
|
+
const { account, name } = accountView(row, options);
|
|
434
|
+
const own = record(floors[row.id]);
|
|
435
|
+
const set = labels.filter((label) => typeof own[label] === 'number');
|
|
436
|
+
return {
|
|
437
|
+
id: row.id,
|
|
438
|
+
label: name,
|
|
439
|
+
group: `Floors · killswitch ${state}`,
|
|
440
|
+
// A floor is the least "% left" the account may fall to.
|
|
441
|
+
status: set.length === 0
|
|
442
|
+
? 'no floors'
|
|
443
|
+
: set.map((label) => `${nameOf(label)} ≥${own[label]}%`).join(' · '),
|
|
444
|
+
account,
|
|
445
|
+
actions: [
|
|
446
|
+
{
|
|
447
|
+
id: 'floors',
|
|
448
|
+
label: 'Set floors',
|
|
449
|
+
knobs: labels.map((label) => ({
|
|
450
|
+
kind: 'number',
|
|
451
|
+
id: label,
|
|
452
|
+
label: `Minimum % left for ${nameOf(label)}`,
|
|
453
|
+
min: 0,
|
|
454
|
+
max: 100,
|
|
455
|
+
...(typeof own[label] === 'number'
|
|
456
|
+
? { value: own[label] }
|
|
457
|
+
: {}),
|
|
458
|
+
})),
|
|
459
|
+
run: async ({ values }) => {
|
|
460
|
+
await store.updateSettings((settings) => {
|
|
461
|
+
const next = record(settings.killswitch);
|
|
462
|
+
const accounts = record(next.accounts);
|
|
463
|
+
const mine = record(accounts[row.id]);
|
|
464
|
+
for (const label of labels) {
|
|
465
|
+
const value = values[label];
|
|
466
|
+
if (typeof value === 'number')
|
|
467
|
+
mine[label] = value;
|
|
468
|
+
else if (value === null)
|
|
469
|
+
delete mine[label];
|
|
470
|
+
}
|
|
471
|
+
if (Object.keys(mine).length > 0)
|
|
472
|
+
accounts[row.id] = mine;
|
|
473
|
+
else
|
|
474
|
+
delete accounts[row.id];
|
|
475
|
+
if (Object.keys(accounts).length > 0)
|
|
476
|
+
next.accounts = accounts;
|
|
477
|
+
else
|
|
478
|
+
delete next.accounts;
|
|
479
|
+
settings.killswitch = next;
|
|
480
|
+
return undefined;
|
|
481
|
+
}, locks);
|
|
482
|
+
return `Floors updated for ${name}.`;
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
],
|
|
486
|
+
};
|
|
487
|
+
});
|
|
488
|
+
return {
|
|
489
|
+
id: 'limits',
|
|
490
|
+
slot: 'limits',
|
|
491
|
+
title: 'Limits',
|
|
492
|
+
content: {
|
|
493
|
+
lines: [`Killswitch ${state}`],
|
|
494
|
+
items,
|
|
495
|
+
actions: [
|
|
496
|
+
{
|
|
497
|
+
id: 'killswitch',
|
|
498
|
+
label: enabled ? 'Turn killswitch off' : 'Turn killswitch on',
|
|
499
|
+
description: 'With the killswitch on, an account whose quota falls below one of its floors is not used.',
|
|
500
|
+
group: ACTIONS_GROUP,
|
|
501
|
+
knobs: [
|
|
502
|
+
{
|
|
503
|
+
kind: 'toggle',
|
|
504
|
+
id: 'enabled',
|
|
505
|
+
label: 'Killswitch',
|
|
506
|
+
value: !enabled,
|
|
507
|
+
},
|
|
508
|
+
],
|
|
509
|
+
run: async ({ values }) => {
|
|
510
|
+
const on = values.enabled === true;
|
|
511
|
+
await store.updateSettings((settings) => {
|
|
512
|
+
settings.killswitch = {
|
|
513
|
+
...record(settings.killswitch),
|
|
514
|
+
enabled: on,
|
|
515
|
+
};
|
|
516
|
+
return undefined;
|
|
517
|
+
}, locks);
|
|
518
|
+
return `Killswitch ${on ? 'on' : 'off'}.`;
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
],
|
|
522
|
+
},
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
/** The four built-in sections, in their fixed order, from one read of the store. */
|
|
526
|
+
export async function builtinSections(options) {
|
|
527
|
+
const snap = await snapshot(options.store);
|
|
528
|
+
return [
|
|
529
|
+
accountsSection(options, snap),
|
|
530
|
+
quotaSection(options, snap),
|
|
531
|
+
routingSection(options, snap),
|
|
532
|
+
limitsSection(options, snap),
|
|
533
|
+
];
|
|
534
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Prefix of a refresh quarantine reason, followed by JSON of the expected and returned identities. */
|
|
2
|
+
export const IDENTITY_CONTRADICTED_REASON_PREFIX = 'identity-contradicted: ';
|
|
3
|
+
/** The reason recorded on a row disabled because an earlier row is the same account. */
|
|
4
|
+
export const DUPLICATE_IDENTITY_REASON = 'duplicate-identity';
|
|
5
|
+
/**
|
|
6
|
+
* Enabled OAuth rows holding a credential whose wire identity is not yet
|
|
7
|
+
* known. API-key rows and disabled rows never count.
|
|
8
|
+
*/
|
|
9
|
+
export function countUnknownIdentityRows(rows) {
|
|
10
|
+
return rows.filter((row) => row.invalid === undefined &&
|
|
11
|
+
!row.staged &&
|
|
12
|
+
row.type === 'oauth' &&
|
|
13
|
+
row.enabled &&
|
|
14
|
+
row.credential !== undefined &&
|
|
15
|
+
row.identity === undefined).length;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Marks a row disabled with a reason: `enabled: false` in the roster row,
|
|
19
|
+
* which older readers honour, and the reason in the per-row entry. A row
|
|
20
|
+
* without an entry gets one at epoch 1. Nothing is ever deleted.
|
|
21
|
+
*/
|
|
22
|
+
export function disableIn(tx, id, reason) {
|
|
23
|
+
const raw = tx.rosterRow(id);
|
|
24
|
+
if (!raw)
|
|
25
|
+
return;
|
|
26
|
+
raw.enabled = false;
|
|
27
|
+
const entry = tx.entry(id) ?? { credentialEpoch: 1, needsFirstReading: true };
|
|
28
|
+
// A routine disable must not erase the evidence needed to refuse enable.
|
|
29
|
+
const quarantined = typeof entry.disabledReason === 'string' &&
|
|
30
|
+
entry.disabledReason.startsWith(IDENTITY_CONTRADICTED_REASON_PREFIX);
|
|
31
|
+
tx.setEntry(id, {
|
|
32
|
+
...entry,
|
|
33
|
+
disabledReason: quarantined ? entry.disabledReason : reason,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Marks a row enabled: `enabled: true` in the roster row and no
|
|
38
|
+
* `disabledReason` in its entry. A row without an entry is not given one.
|
|
39
|
+
*/
|
|
40
|
+
export function enableIn(tx, id) {
|
|
41
|
+
const raw = tx.rosterRow(id);
|
|
42
|
+
if (!raw)
|
|
43
|
+
return;
|
|
44
|
+
raw.enabled = true;
|
|
45
|
+
const entry = tx.entry(id);
|
|
46
|
+
if (entry && 'disabledReason' in entry) {
|
|
47
|
+
const next = { ...entry };
|
|
48
|
+
delete next.disabledReason;
|
|
49
|
+
tx.setEntry(id, next);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Two enabled OAuth rows with one wire identity are the same account: the
|
|
54
|
+
* earlier row in roster order stays enabled and every later one is disabled
|
|
55
|
+
* with a reason. Returns the ids it disabled.
|
|
56
|
+
*/
|
|
57
|
+
export function disableIdentityDuplicates(tx, identity) {
|
|
58
|
+
const holders = tx
|
|
59
|
+
.rows()
|
|
60
|
+
.filter((row) => row.invalid === undefined &&
|
|
61
|
+
!row.staged &&
|
|
62
|
+
row.type === 'oauth' &&
|
|
63
|
+
row.enabled &&
|
|
64
|
+
row.identity === identity);
|
|
65
|
+
const disabled = [];
|
|
66
|
+
for (const row of holders.slice(1)) {
|
|
67
|
+
disableIn(tx, row.id, DUPLICATE_IDENTITY_REASON);
|
|
68
|
+
disabled.push(row.id);
|
|
69
|
+
}
|
|
70
|
+
return disabled;
|
|
71
|
+
}
|
|
72
|
+
/** Records a row's wire identity in its roster row, then applies dedupe. */
|
|
73
|
+
export function recordIdentityIn(tx, id, identity) {
|
|
74
|
+
const raw = tx.rosterRow(id);
|
|
75
|
+
if (!raw)
|
|
76
|
+
return [];
|
|
77
|
+
raw.accountId = identity;
|
|
78
|
+
return disableIdentityDuplicates(tx, identity);
|
|
79
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createSidebarFile } from "./c64d7afbc14ca125-sidebar-file.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// The command menu: sections of items, each carrying actions with typed
|
|
2
|
+
// inputs. Two kinds of type live here. The `Menu*` types and the request and
|
|
3
|
+
// result types are plain data: they cross the loopback RPC to a host's TUI
|
|
4
|
+
// and are what an in-process renderer walks. The `*Definition` types carry
|
|
5
|
+
// the action bodies a plugin or the library supplies; they never leave the
|
|
6
|
+
// process and are turned into the plain types only by the seam in `seam.ts`.
|
|
7
|
+
/** The section slots, in the one order every renderer shows them. */
|
|
8
|
+
export const SECTION_SLOTS = [
|
|
9
|
+
'accounts',
|
|
10
|
+
'quota',
|
|
11
|
+
'routing',
|
|
12
|
+
'limits',
|
|
13
|
+
'cache',
|
|
14
|
+
'diagnostics',
|
|
15
|
+
'extra',
|
|
16
|
+
];
|