@cortexkit/antigravity-auth-core 2.2.1 → 3.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.
Files changed (253) hide show
  1. package/README.md +24 -2
  2. package/dist/abort-forwarding.d.ts +3 -0
  3. package/dist/abort-forwarding.d.ts.map +1 -0
  4. package/dist/abort-forwarding.js +19 -0
  5. package/dist/abort-forwarding.js.map +1 -0
  6. package/dist/account-identity.d.ts +52 -0
  7. package/dist/account-identity.d.ts.map +1 -0
  8. package/dist/account-identity.js +66 -0
  9. package/dist/account-identity.js.map +1 -0
  10. package/dist/account-manager.d.ts +228 -43
  11. package/dist/account-manager.d.ts.map +1 -1
  12. package/dist/account-manager.js +942 -903
  13. package/dist/account-manager.js.map +1 -1
  14. package/dist/account-migration.d.ts +309 -0
  15. package/dist/account-migration.d.ts.map +1 -0
  16. package/dist/account-migration.js +2284 -0
  17. package/dist/account-migration.js.map +1 -0
  18. package/dist/account-repository-codecs.d.ts +82 -0
  19. package/dist/account-repository-codecs.d.ts.map +1 -0
  20. package/dist/account-repository-codecs.js +609 -0
  21. package/dist/account-repository-codecs.js.map +1 -0
  22. package/dist/account-repository-types.d.ts +831 -0
  23. package/dist/account-repository-types.d.ts.map +1 -0
  24. package/dist/account-repository-types.js +137 -0
  25. package/dist/account-repository-types.js.map +1 -0
  26. package/dist/account-repository.d.ts +442 -0
  27. package/dist/account-repository.d.ts.map +1 -0
  28. package/dist/account-repository.js +2253 -0
  29. package/dist/account-repository.js.map +1 -0
  30. package/dist/account-selector.d.ts +470 -0
  31. package/dist/account-selector.d.ts.map +1 -0
  32. package/dist/account-selector.js +1383 -0
  33. package/dist/account-selector.js.map +1 -0
  34. package/dist/account-storage.d.ts.map +1 -1
  35. package/dist/account-storage.js +5 -0
  36. package/dist/account-storage.js.map +1 -1
  37. package/dist/agy-request-metadata.d.ts.map +1 -1
  38. package/dist/agy-request-metadata.js +8 -21
  39. package/dist/agy-request-metadata.js.map +1 -1
  40. package/dist/agy-transport.d.ts.map +1 -1
  41. package/dist/agy-transport.js +21 -35
  42. package/dist/agy-transport.js.map +1 -1
  43. package/dist/antigravity-command-menu.d.ts +171 -0
  44. package/dist/antigravity-command-menu.d.ts.map +1 -0
  45. package/dist/antigravity-command-menu.js +523 -0
  46. package/dist/antigravity-command-menu.js.map +1 -0
  47. package/dist/atomic-write.d.ts +18 -1
  48. package/dist/atomic-write.d.ts.map +1 -1
  49. package/dist/atomic-write.js +15 -3
  50. package/dist/atomic-write.js.map +1 -1
  51. package/dist/common-auth-embedded/NOTICE.txt +21 -0
  52. package/dist/common-auth-embedded/auth-menu/accounts.d.ts +97 -0
  53. package/dist/common-auth-embedded/auth-menu/accounts.js +387 -0
  54. package/dist/common-auth-embedded/auth-menu/ansi.d.ts +26 -0
  55. package/dist/common-auth-embedded/auth-menu/ansi.js +98 -0
  56. package/dist/common-auth-embedded/auth-menu/confirm.d.ts +7 -0
  57. package/dist/common-auth-embedded/auth-menu/confirm.js +21 -0
  58. package/dist/common-auth-embedded/auth-menu/doctor.d.ts +52 -0
  59. package/dist/common-auth-embedded/auth-menu/doctor.js +81 -0
  60. package/dist/common-auth-embedded/auth-menu/index.d.ts +17 -0
  61. package/dist/common-auth-embedded/auth-menu/index.js +12 -0
  62. package/dist/common-auth-embedded/auth-menu/login.d.ts +55 -0
  63. package/dist/common-auth-embedded/auth-menu/login.js +83 -0
  64. package/dist/common-auth-embedded/auth-menu/menu.d.ts +59 -0
  65. package/dist/common-auth-embedded/auth-menu/menu.js +73 -0
  66. package/dist/common-auth-embedded/auth-menu/opencode-v1.d.ts +49 -0
  67. package/dist/common-auth-embedded/auth-menu/opencode-v1.js +47 -0
  68. package/dist/common-auth-embedded/auth-menu/select.d.ts +21 -0
  69. package/dist/common-auth-embedded/auth-menu/select.js +159 -0
  70. package/dist/common-auth-embedded/auth-menu/terminal.d.ts +43 -0
  71. package/dist/common-auth-embedded/auth-menu/terminal.js +19 -0
  72. package/dist/common-auth-embedded/claustrum/consumer.d.ts +102 -0
  73. package/dist/common-auth-embedded/claustrum/consumer.js +298 -0
  74. package/dist/common-auth-embedded/claustrum/custody.d.ts +175 -0
  75. package/dist/common-auth-embedded/claustrum/custody.js +354 -0
  76. package/dist/common-auth-embedded/claustrum/enrollment.d.ts +158 -0
  77. package/dist/common-auth-embedded/claustrum/enrollment.js +759 -0
  78. package/dist/common-auth-embedded/claustrum/errors.d.ts +16 -0
  79. package/dist/common-auth-embedded/claustrum/errors.js +8 -0
  80. package/dist/common-auth-embedded/claustrum/host-slot.d.ts +39 -0
  81. package/dist/common-auth-embedded/claustrum/host-slot.js +72 -0
  82. package/dist/common-auth-embedded/claustrum/index.d.ts +18 -0
  83. package/dist/common-auth-embedded/claustrum/index.js +22 -0
  84. package/dist/common-auth-embedded/claustrum/interlock.d.ts +27 -0
  85. package/dist/common-auth-embedded/claustrum/interlock.js +29 -0
  86. package/dist/common-auth-embedded/claustrum/roster.d.ts +208 -0
  87. package/dist/common-auth-embedded/claustrum/roster.js +536 -0
  88. package/dist/common-auth-embedded/commands/builtins.d.ts +71 -0
  89. package/dist/common-auth-embedded/commands/builtins.js +534 -0
  90. package/dist/common-auth-embedded/commands/index.d.ts +10 -0
  91. package/dist/common-auth-embedded/commands/index.js +5 -0
  92. package/dist/common-auth-embedded/commands/menu.d.ts +61 -0
  93. package/dist/common-auth-embedded/commands/menu.js +300 -0
  94. package/dist/common-auth-embedded/commands/model.d.ts +238 -0
  95. package/dist/common-auth-embedded/commands/model.js +16 -0
  96. package/dist/common-auth-embedded/commands/pi.d.ts +23 -0
  97. package/dist/common-auth-embedded/commands/pi.js +206 -0
  98. package/dist/common-auth-embedded/commands/seam.d.ts +72 -0
  99. package/dist/common-auth-embedded/commands/seam.js +301 -0
  100. package/dist/common-auth-embedded/fs/atomic-write.d.ts +20 -0
  101. package/dist/common-auth-embedded/fs/atomic-write.js +80 -0
  102. package/dist/common-auth-embedded/fs/index.d.ts +7 -0
  103. package/dist/common-auth-embedded/fs/index.js +6 -0
  104. package/dist/common-auth-embedded/fs/lock-constants.d.ts +14 -0
  105. package/dist/common-auth-embedded/fs/lock-constants.js +14 -0
  106. package/dist/common-auth-embedded/fs/refresh-file-lock.d.ts +33 -0
  107. package/dist/common-auth-embedded/fs/refresh-file-lock.js +501 -0
  108. package/dist/common-auth-embedded/fs/with-lock.d.ts +33 -0
  109. package/dist/common-auth-embedded/fs/with-lock.js +48 -0
  110. package/dist/common-auth-embedded/logger/capture-sink.d.ts +14 -0
  111. package/dist/common-auth-embedded/logger/capture-sink.js +13 -0
  112. package/dist/common-auth-embedded/logger/engine.d.ts +100 -0
  113. package/dist/common-auth-embedded/logger/engine.js +246 -0
  114. package/dist/common-auth-embedded/logger/index.d.ts +6 -0
  115. package/dist/common-auth-embedded/logger/index.js +3 -0
  116. package/dist/common-auth-embedded/logger/redact.d.ts +11 -0
  117. package/dist/common-auth-embedded/logger/redact.js +83 -0
  118. package/dist/common-auth-embedded/pi-slot/index.d.ts +3 -0
  119. package/dist/common-auth-embedded/pi-slot/index.js +2 -0
  120. package/dist/common-auth-embedded/pi-slot/lock.js +212 -0
  121. package/dist/common-auth-embedded/pi-slot/slot.d.ts +49 -0
  122. package/dist/common-auth-embedded/pi-slot/slot.js +383 -0
  123. package/dist/common-auth-embedded/quota/codec.d.ts +11 -0
  124. package/dist/common-auth-embedded/quota/codec.js +12 -0
  125. package/dist/common-auth-embedded/quota/format.d.ts +46 -0
  126. package/dist/common-auth-embedded/quota/format.js +138 -0
  127. package/dist/common-auth-embedded/quota/index.d.ts +10 -0
  128. package/dist/common-auth-embedded/quota/index.js +5 -0
  129. package/dist/common-auth-embedded/quota/map.d.ts +63 -0
  130. package/dist/common-auth-embedded/quota/map.js +105 -0
  131. package/dist/common-auth-embedded/quota/merge.d.ts +48 -0
  132. package/dist/common-auth-embedded/quota/merge.js +162 -0
  133. package/dist/common-auth-embedded/quota/projection.d.ts +64 -0
  134. package/dist/common-auth-embedded/quota/projection.js +134 -0
  135. package/dist/common-auth-embedded/routing/admission.d.ts +93 -0
  136. package/dist/common-auth-embedded/routing/admission.js +142 -0
  137. package/dist/common-auth-embedded/routing/index.d.ts +8 -0
  138. package/dist/common-auth-embedded/routing/index.js +4 -0
  139. package/dist/common-auth-embedded/routing/ordered.d.ts +47 -0
  140. package/dist/common-auth-embedded/routing/ordered.js +58 -0
  141. package/dist/common-auth-embedded/routing/pins.d.ts +22 -0
  142. package/dist/common-auth-embedded/routing/pins.js +34 -0
  143. package/dist/common-auth-embedded/routing/sticky.d.ts +158 -0
  144. package/dist/common-auth-embedded/routing/sticky.js +339 -0
  145. package/dist/common-auth-embedded/source-output.json +1090 -0
  146. package/dist/common-auth-embedded/store/attribution.d.ts +28 -0
  147. package/dist/common-auth-embedded/store/attribution.js +60 -0
  148. package/dist/common-auth-embedded/store/errors.d.ts +55 -0
  149. package/dist/common-auth-embedded/store/errors.js +38 -0
  150. package/dist/common-auth-embedded/store/hooks.d.ts +12 -0
  151. package/dist/common-auth-embedded/store/hooks.js +34 -0
  152. package/dist/common-auth-embedded/store/identity.d.ts +39 -0
  153. package/dist/common-auth-embedded/store/identity.js +79 -0
  154. package/dist/common-auth-embedded/store/index.d.ts +21 -0
  155. package/dist/common-auth-embedded/store/index.js +7 -0
  156. package/dist/common-auth-embedded/store/mutate.d.ts +189 -0
  157. package/dist/common-auth-embedded/store/mutate.js +500 -0
  158. package/dist/common-auth-embedded/store/pool.d.ts +196 -0
  159. package/dist/common-auth-embedded/store/pool.js +120 -0
  160. package/dist/common-auth-embedded/store/provider-state.d.ts +131 -0
  161. package/dist/common-auth-embedded/store/provider-state.js +226 -0
  162. package/dist/common-auth-embedded/store/publication.d.ts +47 -0
  163. package/dist/common-auth-embedded/store/publication.js +304 -0
  164. package/dist/common-auth-embedded/store/pull.d.ts +36 -0
  165. package/dist/common-auth-embedded/store/pull.js +143 -0
  166. package/dist/common-auth-embedded/store/refresh-lock.d.ts +63 -0
  167. package/dist/common-auth-embedded/store/refresh-lock.js +136 -0
  168. package/dist/common-auth-embedded/store/refresh.d.ts +72 -0
  169. package/dist/common-auth-embedded/store/refresh.js +225 -0
  170. package/dist/common-auth-embedded/store/reserved.js +13 -0
  171. package/dist/common-auth-embedded/store/rows.d.ts +313 -0
  172. package/dist/common-auth-embedded/store/rows.js +1258 -0
  173. package/dist/common-auth-embedded/store/runtime.d.ts +36 -0
  174. package/dist/common-auth-embedded/store/runtime.js +68 -0
  175. package/dist/common-auth-embedded/store/schema.d.ts +443 -0
  176. package/dist/common-auth-embedded/store/schema.js +754 -0
  177. package/dist/common-auth-embedded/store/settings.d.ts +63 -0
  178. package/dist/common-auth-embedded/store/settings.js +120 -0
  179. package/dist/common-auth-embedded/store/torn.d.ts +78 -0
  180. package/dist/common-auth-embedded/store/torn.js +294 -0
  181. package/dist/common-auth-embedded/util/any-signal.js +42 -0
  182. package/dist/common-auth-runtime.d.ts +73 -0
  183. package/dist/common-auth-runtime.d.ts.map +1 -0
  184. package/dist/common-auth-runtime.js +65 -0
  185. package/dist/common-auth-runtime.js.map +1 -0
  186. package/dist/credential-redaction.d.ts +13 -0
  187. package/dist/credential-redaction.d.ts.map +1 -0
  188. package/dist/credential-redaction.js +230 -0
  189. package/dist/credential-redaction.js.map +1 -0
  190. package/dist/fetch-timeout.d.ts +4 -2
  191. package/dist/fetch-timeout.d.ts.map +1 -1
  192. package/dist/fetch-timeout.js +121 -14
  193. package/dist/fetch-timeout.js.map +1 -1
  194. package/dist/fingerprint.d.ts +3 -3
  195. package/dist/fingerprint.d.ts.map +1 -1
  196. package/dist/fingerprint.js +4 -4
  197. package/dist/fingerprint.js.map +1 -1
  198. package/dist/index.d.ts +16 -1
  199. package/dist/index.d.ts.map +1 -1
  200. package/dist/index.js +15 -0
  201. package/dist/index.js.map +1 -1
  202. package/dist/model-registry.d.ts +16 -0
  203. package/dist/model-registry.d.ts.map +1 -1
  204. package/dist/model-registry.js +242 -30
  205. package/dist/model-registry.js.map +1 -1
  206. package/dist/quota-manager.d.ts.map +1 -1
  207. package/dist/quota-manager.js +35 -10
  208. package/dist/quota-manager.js.map +1 -1
  209. package/dist/quota-presentation.d.ts +10 -0
  210. package/dist/quota-presentation.d.ts.map +1 -0
  211. package/dist/quota-presentation.js +60 -0
  212. package/dist/quota-presentation.js.map +1 -0
  213. package/dist/quota-types.d.ts +2 -0
  214. package/dist/quota-types.d.ts.map +1 -1
  215. package/dist/rotation.d.ts +8 -0
  216. package/dist/rotation.d.ts.map +1 -1
  217. package/dist/rotation.js +24 -0
  218. package/dist/rotation.js.map +1 -1
  219. package/dist/transform/agy-generation.d.ts +3 -0
  220. package/dist/transform/agy-generation.d.ts.map +1 -0
  221. package/dist/transform/agy-generation.js +28 -0
  222. package/dist/transform/agy-generation.js.map +1 -0
  223. package/dist/transform/claude.d.ts +7 -3
  224. package/dist/transform/claude.d.ts.map +1 -1
  225. package/dist/transform/claude.js +8 -2
  226. package/dist/transform/claude.js.map +1 -1
  227. package/dist/transform/index.d.ts +1 -0
  228. package/dist/transform/index.d.ts.map +1 -1
  229. package/dist/transform/index.js +1 -0
  230. package/dist/transform/index.js.map +1 -1
  231. package/dist/transform/model-resolver.d.ts +2 -1
  232. package/dist/transform/model-resolver.d.ts.map +1 -1
  233. package/dist/transform/model-resolver.js +46 -5
  234. package/dist/transform/model-resolver.js.map +1 -1
  235. package/dist/transform/types.d.ts +1 -1
  236. package/dist/transform/types.d.ts.map +1 -1
  237. package/dist/vault-account-source.d.ts +183 -0
  238. package/dist/vault-account-source.d.ts.map +1 -0
  239. package/dist/vault-account-source.js +296 -0
  240. package/dist/vault-account-source.js.map +1 -0
  241. package/dist/vault-enrollment-authority.d.ts +16 -0
  242. package/dist/vault-enrollment-authority.d.ts.map +1 -0
  243. package/dist/vault-enrollment-authority.js +19 -0
  244. package/dist/vault-enrollment-authority.js.map +1 -0
  245. package/dist/vault-family.d.ts +11 -0
  246. package/dist/vault-family.d.ts.map +1 -0
  247. package/dist/vault-family.js +24 -0
  248. package/dist/vault-family.js.map +1 -0
  249. package/dist/vault-provider-state.d.ts +162 -0
  250. package/dist/vault-provider-state.d.ts.map +1 -0
  251. package/dist/vault-provider-state.js +422 -0
  252. package/dist/vault-provider-state.js.map +1 -0
  253. package/package.json +39 -2
@@ -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 '../quota/index.js';
6
+ import { DEFAULT_FORMER_MAIN_ID, orderForPlacement, resolveRoutingMode, } from '../routing/index.js';
7
+ import { projectFailure, } from './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,10 @@
1
+ export type { AccountsSectionOptions, LimitsSectionOptions, LoginOutcome, QuotaSectionOptions, RoutingSectionOptions, } from './builtins.js';
2
+ export { MENU_DISABLED_REASON } from './builtins.js';
3
+ export type { CommandMenu, CommandMenuOptions, StoreSectionSlot, } from './menu.js';
4
+ export { createCommandMenu, parseApplyRequest } from './menu.js';
5
+ export type { ActionDefinition, ActionInput, ActionOutcome, CommandApplyRequest, CommandApplyResult, CommandDialogPayload, CommandInvocation, CommandMenuModel, ItemDefinition, KnobValue, KnobValues, MenuAccount, MenuAction, MenuChoice, MenuConfirmation, MenuItem, MenuKnob, MenuSection, NotifyKind, PluginExtraSection, PluginSection, SectionContent, SectionSlot, } from './model.js';
6
+ export { SECTION_SLOTS } from './model.js';
7
+ export type { PiMenuOptions, PiMenuUi } from './pi.js';
8
+ export { runPiCommandMenu } from './pi.js';
9
+ export type { ProjectedFailure, SeamLogger, TextRedactor } from './seam.js';
10
+ export { ACTION_FAILED, CommandError, createTextRedactor, DEFAULT_IRREVERSIBLE_CONFIRMATION, projectFailure, } from './seam.js';
@@ -0,0 +1,5 @@
1
+ export { MENU_DISABLED_REASON } from './builtins.js';
2
+ export { createCommandMenu, parseApplyRequest } from './menu.js';
3
+ export { SECTION_SLOTS } from './model.js';
4
+ export { runPiCommandMenu } from './pi.js';
5
+ export { ACTION_FAILED, CommandError, createTextRedactor, DEFAULT_IRREVERSIBLE_CONFIRMATION, projectFailure, } from './seam.js';
@@ -0,0 +1,61 @@
1
+ import { type RedactionOptions } from '../logger/index.js';
2
+ import type { PoolLockSpec, PoolStore } from '../store/index.js';
3
+ import { type AccountsSectionOptions, type LimitsSectionOptions, type QuotaSectionOptions, type RoutingSectionOptions } from './builtins.js';
4
+ import type { CommandApplyRequest, CommandApplyResult, CommandDialogPayload, CommandInvocation, PluginExtraSection, PluginSection } from './model.js';
5
+ import { type SeamLogger } from './seam.js';
6
+ /** The four slots the library can build from a pool store. */
7
+ export type StoreSectionSlot = 'accounts' | 'quota' | 'routing' | 'limits';
8
+ export interface CommandMenuOptions {
9
+ /** The slash command's name without the slash (`openai`, `claude`). */
10
+ command: string;
11
+ /** The dialog title. */
12
+ title: string;
13
+ /**
14
+ * The pool store the built-in Accounts, Quota, Routing and Limits sections
15
+ * read and write. Required unless `replace` supplies all four.
16
+ */
17
+ store?: PoolStore;
18
+ /**
19
+ * Plugin sections that take the place of built-in ones, keeping their slot
20
+ * and the fixed order. For a plugin whose accounts are not pool rows (a
21
+ * single host login, or rows it must not expose by id), so it renders its
22
+ * own items and actions without a store. A replaced slot's built-in
23
+ * options (`accounts`, `quota`, ...) must not be given.
24
+ */
25
+ replace?: Partial<Record<StoreSectionSlot, PluginSection>>;
26
+ /** The plugin's legacy locks, passed to every store write the menu makes. */
27
+ extraLocks?: readonly PoolLockSpec[];
28
+ accounts?: AccountsSectionOptions;
29
+ quota?: QuotaSectionOptions;
30
+ routing?: RoutingSectionOptions;
31
+ limits?: LimitsSectionOptions;
32
+ /** The Cache section; omitted when the plugin has none. */
33
+ cache?: PluginSection;
34
+ /** The Diagnostics section (dumps, logging); omitted when the plugin has none. */
35
+ diagnostics?: PluginSection;
36
+ /** Provider extras, shown after every fixed section in this order. */
37
+ extras?: readonly PluginExtraSection[];
38
+ /** Receives the seam's warnings and failed actions; defaults to the library logger. */
39
+ logger?: SeamLogger;
40
+ /**
41
+ * The provider's secret shapes, added to the redactor every string that
42
+ * leaves the menu goes through (payloads and notifications). Without a
43
+ * pattern for its key format, a plugin's API key quoted in an outcome's
44
+ * text is not recognised.
45
+ */
46
+ redaction?: RedactionOptions;
47
+ now?: () => number;
48
+ }
49
+ export interface CommandMenu {
50
+ readonly command: string;
51
+ /** The dialog payload for one invocation of the slash command. */
52
+ open(invocation: CommandInvocation): Promise<CommandDialogPayload>;
53
+ /** Applies one action and returns its message and the refreshed menu. */
54
+ apply(request: CommandApplyRequest, invocation: CommandInvocation): Promise<CommandApplyResult>;
55
+ }
56
+ export declare function createCommandMenu(options: CommandMenuOptions): CommandMenu;
57
+ /**
58
+ * Checks an apply request that arrived over the loopback RPC; undefined when
59
+ * it is not one. Only the request's own fields are kept.
60
+ */
61
+ export declare function parseApplyRequest(value: unknown): CommandApplyRequest | undefined;