orizu 0.5.24 → 0.5.26
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 +23 -3
- package/dist/credentials.d.ts +15 -0
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +279 -59
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +5 -2
- package/dist/http.d.ts +20 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +239 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +153 -213
- package/dist/keyboard-select.d.ts +7 -0
- package/dist/keyboard-select.d.ts.map +1 -0
- package/dist/keyboard-select.js +72 -0
- package/dist/logout-diagnostic.d.ts +4 -0
- package/dist/logout-diagnostic.d.ts.map +1 -0
- package/dist/logout-diagnostic.js +26 -0
- package/dist/setup-account.d.ts +11 -0
- package/dist/setup-account.d.ts.map +1 -0
- package/dist/setup-account.js +106 -0
- package/dist/setup-onboarding.d.ts +27 -1
- package/dist/setup-onboarding.d.ts.map +1 -1
- package/dist/setup-onboarding.js +95 -11
- package/dist/setup-summary.d.ts +12 -0
- package/dist/setup-summary.d.ts.map +1 -0
- package/dist/setup-summary.js +56 -0
- package/package.json +1 -1
- package/src/credentials.ts +326 -57
- package/src/help.ts +7 -2
- package/src/http.ts +325 -20
- package/src/index.ts +193 -279
- package/src/keyboard-select.ts +97 -0
- package/src/logout-diagnostic.ts +39 -0
- package/src/setup-account.ts +129 -0
- package/src/setup-onboarding.ts +133 -10
- package/src/setup-summary.ts +77 -0
- package/vendor/skills/orizu/SKILL.md +51 -111
- package/vendor/skills/orizu/flows/expand.md +27 -0
- package/vendor/skills/orizu/flows/first-win.md +31 -0
- package/vendor/skills/orizu/flows/onboard.md +31 -0
- package/vendor/skills/orizu/flows/promote.md +33 -0
- package/vendor/skills/orizu/flows/recurse.md +23 -0
- package/vendor/skills/orizu/flows/triage.md +37 -0
- package/vendor/skills/orizu/references/assess-and-plan.md +5 -5
- package/vendor/skills/orizu/references/authority-map.md +20 -0
- package/vendor/skills/orizu/references/building-apps.md +7 -1
- package/vendor/skills/orizu/references/building-judges.md +15 -16
- package/vendor/skills/orizu/references/cli-reference.md +17 -8
- package/vendor/skills/orizu/references/dataset-design.md +16 -6
- package/vendor/skills/orizu/references/eval-strategy.md +18 -15
- package/vendor/skills/orizu/references/instructions-after-prompts.md +2 -2
- package/vendor/skills/orizu/references/migrate-existing-gepa-setup.md +10 -6
- package/vendor/skills/orizu/references/optimization-reports.md +11 -11
- package/vendor/skills/orizu/references/optimization-with-gepa.md +4 -2
- package/vendor/skills/orizu/references/primer.md +18 -22
- package/vendor/skills/orizu/references/prompt-control-plane.md +5 -5
- package/vendor/skills/orizu/references/vocabulary.md +27 -0
package/dist/http.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { getActiveBaseUrl, getServerCredentials, resolveEnvBearerToken,
|
|
1
|
+
import { clearServerCredentialsIfCurrent, credentialsEqual, getActiveBaseUrl, getServerCredentials, resolveEnvBearerToken, updateServerCredentialsIfCurrent, withCredentialsTransactionLockAsync, } from './credentials.js';
|
|
2
2
|
import { getFlagBaseUrl, normalizeBaseUrl } from './global-flags.js';
|
|
3
|
+
import { describeLogoutHttpFailure, describeLogoutTransportFailure, } from './logout-diagnostic.js';
|
|
3
4
|
let runtimeFlags = { local: false, server: null };
|
|
4
5
|
export function setGlobalFlags(flags) {
|
|
5
6
|
runtimeFlags = flags;
|
|
@@ -65,24 +66,79 @@ function isExpired(expiresAt) {
|
|
|
65
66
|
const nowUnix = Math.floor(Date.now() / 1000);
|
|
66
67
|
return expiresAt <= nowUnix + 30;
|
|
67
68
|
}
|
|
69
|
+
const DEFAULT_CREDENTIALS_REFRESH_TIMEOUT_MS = 10_000;
|
|
70
|
+
const MAX_CREDENTIALS_REFRESH_TIMEOUT_MS = 30_000;
|
|
71
|
+
const DEFAULT_LOGOUT_TIMEOUT_MS = 10_000;
|
|
72
|
+
const MAX_LOGOUT_TIMEOUT_MS = 30_000;
|
|
73
|
+
function getCredentialsRefreshTimeoutMs() {
|
|
74
|
+
const configured = Number(process.env.ORIZU_CREDENTIALS_REFRESH_TIMEOUT_MS);
|
|
75
|
+
if (!Number.isSafeInteger(configured) || configured <= 0) {
|
|
76
|
+
return DEFAULT_CREDENTIALS_REFRESH_TIMEOUT_MS;
|
|
77
|
+
}
|
|
78
|
+
return Math.min(configured, MAX_CREDENTIALS_REFRESH_TIMEOUT_MS);
|
|
79
|
+
}
|
|
80
|
+
function getLogoutTimeoutMs() {
|
|
81
|
+
const configured = Number(process.env.ORIZU_LOGOUT_TIMEOUT_MS);
|
|
82
|
+
if (!Number.isSafeInteger(configured) || configured <= 0) {
|
|
83
|
+
return DEFAULT_LOGOUT_TIMEOUT_MS;
|
|
84
|
+
}
|
|
85
|
+
return Math.min(configured, MAX_LOGOUT_TIMEOUT_MS);
|
|
86
|
+
}
|
|
87
|
+
async function withAbortDeadline(operation, callerSignal, timeoutMs, timeoutError) {
|
|
88
|
+
const controller = new AbortController();
|
|
89
|
+
const handleCallerAbort = () => controller.abort(callerSignal?.reason);
|
|
90
|
+
if (callerSignal?.aborted)
|
|
91
|
+
handleCallerAbort();
|
|
92
|
+
else
|
|
93
|
+
callerSignal?.addEventListener('abort', handleCallerAbort, { once: true });
|
|
94
|
+
const timer = setTimeout(() => controller.abort(timeoutError), timeoutMs);
|
|
95
|
+
const cancellation = new Promise((_resolve, reject) => {
|
|
96
|
+
const handleAbort = () => reject(controller.signal.reason ?? new Error('Request aborted.'));
|
|
97
|
+
if (controller.signal.aborted)
|
|
98
|
+
handleAbort();
|
|
99
|
+
else
|
|
100
|
+
controller.signal.addEventListener('abort', handleAbort, { once: true });
|
|
101
|
+
});
|
|
102
|
+
try {
|
|
103
|
+
return await Promise.race([operation(controller.signal), cancellation]);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
callerSignal?.removeEventListener('abort', handleCallerAbort);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function withCredentialsRefreshDeadline(operation, callerSignal) {
|
|
111
|
+
const timeoutMs = getCredentialsRefreshTimeoutMs();
|
|
112
|
+
const timeoutError = new Error(`ORIZU_CREDENTIALS_REFRESH_TIMEOUT: session refresh exceeded ${timeoutMs}ms.`);
|
|
113
|
+
return await withAbortDeadline(operation, callerSignal, timeoutMs, timeoutError);
|
|
114
|
+
}
|
|
115
|
+
async function withLogoutDeadline(operation, callerSignal) {
|
|
116
|
+
const timeoutMs = getLogoutTimeoutMs();
|
|
117
|
+
const timeoutError = new Error(`ORIZU_LOGOUT_TIMEOUT: remote logout exceeded ${timeoutMs}ms.`);
|
|
118
|
+
timeoutError.name = 'TimeoutError';
|
|
119
|
+
return await withAbortDeadline(operation, callerSignal, timeoutMs, timeoutError);
|
|
120
|
+
}
|
|
68
121
|
export function credentialRequestRedirectPolicy() {
|
|
69
122
|
return process.env.ORIZU_CLI_AUTH_REDIRECT === 'error' ? { redirect: 'error' } : {};
|
|
70
123
|
}
|
|
71
|
-
async function refreshCredentials(baseUrl, credentials) {
|
|
124
|
+
async function refreshCredentials(baseUrl, credentials, callerSignal) {
|
|
72
125
|
if (!isSessionCredentials(credentials)) {
|
|
73
126
|
throw new Error('API key credentials do not refresh. Run `orizu login` again if access fails.');
|
|
74
127
|
}
|
|
75
128
|
assertSecureTokenTransport(baseUrl);
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
129
|
+
const data = await withCredentialsRefreshDeadline(async (signal) => {
|
|
130
|
+
const response = await fetch(`${baseUrl}/api/cli/auth/refresh`, {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: { 'Content-Type': 'application/json' },
|
|
133
|
+
body: JSON.stringify({ refreshToken: credentials.refreshToken }),
|
|
134
|
+
...credentialRequestRedirectPolicy(),
|
|
135
|
+
signal,
|
|
136
|
+
});
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
throw new Error('Session expired. Run `orizu login` again.');
|
|
139
|
+
}
|
|
140
|
+
return await response.json();
|
|
141
|
+
}, callerSignal);
|
|
86
142
|
if (!data.accessToken || !data.refreshToken || !data.expiresAt) {
|
|
87
143
|
throw new Error('Server returned invalid refresh credentials. Run `orizu login` again.');
|
|
88
144
|
}
|
|
@@ -92,9 +148,169 @@ async function refreshCredentials(baseUrl, credentials) {
|
|
|
92
148
|
refreshToken: data.refreshToken,
|
|
93
149
|
expiresAt: data.expiresAt,
|
|
94
150
|
};
|
|
95
|
-
|
|
151
|
+
if (isExpired(refreshed.expiresAt)) {
|
|
152
|
+
throw new Error('ORIZU_CREDENTIALS_REFRESH_UNUSABLE: refreshed session expires within the 30-second safety window.');
|
|
153
|
+
}
|
|
96
154
|
return refreshed;
|
|
97
155
|
}
|
|
156
|
+
async function refreshStoredCredentialsIfCurrent(baseUrl, expected, callerSignal) {
|
|
157
|
+
return await withCredentialsTransactionLockAsync(async () => {
|
|
158
|
+
const current = getServerCredentials(baseUrl);
|
|
159
|
+
if (!current || !credentialsEqual(current, expected)) {
|
|
160
|
+
return { status: 'changed', credentials: current };
|
|
161
|
+
}
|
|
162
|
+
const refreshed = await refreshCredentials(baseUrl, expected, callerSignal);
|
|
163
|
+
if (!updateServerCredentialsIfCurrent(baseUrl, expected, refreshed)) {
|
|
164
|
+
return { status: 'changed', credentials: getServerCredentials(baseUrl) };
|
|
165
|
+
}
|
|
166
|
+
return { status: 'refreshed', credentials: refreshed };
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function requireCurrentCredentials(baseUrl, credentials) {
|
|
170
|
+
if (credentials)
|
|
171
|
+
return credentials;
|
|
172
|
+
throw new Error(`Not logged in for ${baseUrl}. Run \`orizu login --server ${baseUrl}\` (or \`--local\`) first.`);
|
|
173
|
+
}
|
|
174
|
+
async function refreshExpiredStoredCredentials(baseUrl, initial, callerSignal) {
|
|
175
|
+
if (!isSessionCredentials(initial) || !isExpired(initial.expiresAt)) {
|
|
176
|
+
return { credentials: initial, didRefresh: false };
|
|
177
|
+
}
|
|
178
|
+
const result = await refreshStoredCredentialsIfCurrent(baseUrl, initial, callerSignal);
|
|
179
|
+
if (result.status === 'changed') {
|
|
180
|
+
throw new Error('ORIZU_AUTH_CONTEXT_CHANGED: stored credentials changed before this request began; the request was not sent.');
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
credentials: requireCurrentCredentials(baseUrl, result.credentials),
|
|
184
|
+
didRefresh: true,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Captures one authenticated server context for a multi-step operation. The
|
|
189
|
+
* returned interface intentionally exposes no credential material: its closure
|
|
190
|
+
* pins the selected origin and bearer/session so later global config changes
|
|
191
|
+
* cannot move an in-flight operation to another account.
|
|
192
|
+
*/
|
|
193
|
+
export function captureAuthenticatedRequestContext(options = {}) {
|
|
194
|
+
const baseUrl = resolveBaseUrl();
|
|
195
|
+
const allowRefresh = options.allowRefresh ?? true;
|
|
196
|
+
assertSecureTokenTransport(baseUrl);
|
|
197
|
+
const envBearer = resolveEnvBearerToken();
|
|
198
|
+
if (envBearer) {
|
|
199
|
+
const explicitBearer = process.env.ORIZU_TOKEN?.trim() || null;
|
|
200
|
+
const source = explicitBearer
|
|
201
|
+
? 'ORIZU_TOKEN'
|
|
202
|
+
: 'ORIZU_TOKEN_FILE';
|
|
203
|
+
const resolveRequestBearer = () => {
|
|
204
|
+
if (explicitBearer)
|
|
205
|
+
return explicitBearer;
|
|
206
|
+
try {
|
|
207
|
+
const currentBearer = resolveEnvBearerToken();
|
|
208
|
+
if (currentBearer)
|
|
209
|
+
return currentBearer;
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
213
|
+
throw new Error(`${detail} Fix, rotate, or unset ORIZU_TOKEN_FILE and rerun \`orizu setup\`.`);
|
|
214
|
+
}
|
|
215
|
+
throw new Error('ORIZU_TOKEN_FILE no longer supplies a token. Fix, rotate, or unset ORIZU_TOKEN_FILE and rerun `orizu setup`.');
|
|
216
|
+
};
|
|
217
|
+
return {
|
|
218
|
+
baseUrl,
|
|
219
|
+
source,
|
|
220
|
+
async fetch(path, init = {}) {
|
|
221
|
+
return await fetch(`${baseUrl}${path}`, {
|
|
222
|
+
...init,
|
|
223
|
+
headers: {
|
|
224
|
+
...(init.headers || {}),
|
|
225
|
+
Authorization: `Bearer ${resolveRequestBearer()}`,
|
|
226
|
+
},
|
|
227
|
+
...credentialRequestRedirectPolicy(),
|
|
228
|
+
});
|
|
229
|
+
},
|
|
230
|
+
async logout() {
|
|
231
|
+
throw new Error(`Authentication is supplied by ${source}; stored logout is unavailable.`);
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const capturedCredentials = getServerCredentials(baseUrl);
|
|
236
|
+
if (!capturedCredentials) {
|
|
237
|
+
throw new Error(`Not logged in for ${baseUrl}. Run \`orizu login --server ${baseUrl}\` (or \`--local\`) first.`);
|
|
238
|
+
}
|
|
239
|
+
let activeCredentials = capturedCredentials;
|
|
240
|
+
async function refreshCapturedCredentials(callerSignal) {
|
|
241
|
+
const result = await refreshStoredCredentialsIfCurrent(baseUrl, activeCredentials, callerSignal);
|
|
242
|
+
if (result.status === 'changed') {
|
|
243
|
+
throw new Error('ORIZU_CREDENTIALS_CHANGED: stored credentials changed before the captured session could refresh. Reconfirm the account and retry.');
|
|
244
|
+
}
|
|
245
|
+
activeCredentials = requireCurrentCredentials(baseUrl, result.credentials);
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
baseUrl,
|
|
249
|
+
source: 'stored',
|
|
250
|
+
async fetch(path, init = {}) {
|
|
251
|
+
let didRefresh = false;
|
|
252
|
+
if (allowRefresh
|
|
253
|
+
&& isSessionCredentials(activeCredentials)
|
|
254
|
+
&& isExpired(activeCredentials.expiresAt)) {
|
|
255
|
+
await refreshCapturedCredentials(init.signal);
|
|
256
|
+
didRefresh = true;
|
|
257
|
+
}
|
|
258
|
+
let response = await fetch(`${baseUrl}${path}`, {
|
|
259
|
+
...init,
|
|
260
|
+
headers: {
|
|
261
|
+
...(init.headers || {}),
|
|
262
|
+
Authorization: `Bearer ${getAuthorizationToken(activeCredentials)}`,
|
|
263
|
+
},
|
|
264
|
+
...credentialRequestRedirectPolicy(),
|
|
265
|
+
});
|
|
266
|
+
if (allowRefresh
|
|
267
|
+
&& response.status === 401
|
|
268
|
+
&& isSessionCredentials(activeCredentials)
|
|
269
|
+
&& !didRefresh) {
|
|
270
|
+
await refreshCapturedCredentials(init.signal);
|
|
271
|
+
response = await fetch(`${baseUrl}${path}`, {
|
|
272
|
+
...init,
|
|
273
|
+
headers: {
|
|
274
|
+
...(init.headers || {}),
|
|
275
|
+
Authorization: `Bearer ${getAuthorizationToken(activeCredentials)}`,
|
|
276
|
+
},
|
|
277
|
+
...credentialRequestRedirectPolicy(),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return response;
|
|
281
|
+
},
|
|
282
|
+
async logout(callerSignal) {
|
|
283
|
+
const authorizationToken = getAuthorizationToken(activeCredentials);
|
|
284
|
+
const secrets = [
|
|
285
|
+
authorizationToken,
|
|
286
|
+
...(isSessionCredentials(activeCredentials) ? [activeCredentials.refreshToken] : []),
|
|
287
|
+
];
|
|
288
|
+
let remoteLogoutReason = null;
|
|
289
|
+
try {
|
|
290
|
+
remoteLogoutReason = await withLogoutDeadline(async (signal) => {
|
|
291
|
+
const response = await fetch(`${baseUrl}/api/cli/auth/logout`, {
|
|
292
|
+
method: 'POST',
|
|
293
|
+
headers: {
|
|
294
|
+
'Content-Type': 'application/json',
|
|
295
|
+
Authorization: `Bearer ${authorizationToken}`,
|
|
296
|
+
},
|
|
297
|
+
body: isSessionCredentials(activeCredentials)
|
|
298
|
+
? JSON.stringify({ refreshToken: activeCredentials.refreshToken })
|
|
299
|
+
: undefined,
|
|
300
|
+
...credentialRequestRedirectPolicy(),
|
|
301
|
+
signal,
|
|
302
|
+
});
|
|
303
|
+
return response.ok ? null : await describeLogoutHttpFailure(response, secrets);
|
|
304
|
+
}, callerSignal);
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
remoteLogoutReason = describeLogoutTransportFailure(error, secrets);
|
|
308
|
+
}
|
|
309
|
+
const localCredentialCleared = clearServerCredentialsIfCurrent(baseUrl, activeCredentials);
|
|
310
|
+
return { remoteError: remoteLogoutReason, localCredentialCleared };
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
}
|
|
98
314
|
export async function authedFetch(path, init = {}) {
|
|
99
315
|
const baseUrl = resolveBaseUrl();
|
|
100
316
|
assertSecureTokenTransport(baseUrl);
|
|
@@ -118,10 +334,8 @@ export async function authedFetch(path, init = {}) {
|
|
|
118
334
|
if (!credentials) {
|
|
119
335
|
throw new Error(`Not logged in for ${baseUrl}. Run \`orizu login --server ${baseUrl}\` (or \`--local\`) first.`);
|
|
120
336
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
activeCredentials = await refreshCredentials(baseUrl, activeCredentials);
|
|
124
|
-
}
|
|
337
|
+
const resolved = await refreshExpiredStoredCredentials(baseUrl, credentials, init.signal);
|
|
338
|
+
let activeCredentials = resolved.credentials;
|
|
125
339
|
let response = await fetch(`${baseUrl}${path}`, {
|
|
126
340
|
...init,
|
|
127
341
|
headers: {
|
|
@@ -130,8 +344,14 @@ export async function authedFetch(path, init = {}) {
|
|
|
130
344
|
},
|
|
131
345
|
...credentialRequestRedirectPolicy(),
|
|
132
346
|
});
|
|
133
|
-
if (response.status === 401
|
|
134
|
-
|
|
347
|
+
if (response.status === 401
|
|
348
|
+
&& isSessionCredentials(activeCredentials)
|
|
349
|
+
&& !resolved.didRefresh) {
|
|
350
|
+
const refreshResult = await refreshStoredCredentialsIfCurrent(baseUrl, activeCredentials, init.signal);
|
|
351
|
+
if (refreshResult.status === 'changed') {
|
|
352
|
+
throw new Error('ORIZU_AUTH_CONTEXT_CHANGED: stored credentials changed after this request began; the request was not replayed.');
|
|
353
|
+
}
|
|
354
|
+
activeCredentials = requireCurrentCredentials(baseUrl, refreshResult.credentials);
|
|
135
355
|
response = await fetch(`${baseUrl}${path}`, {
|
|
136
356
|
...init,
|
|
137
357
|
headers: {
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAwHgF,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAoChJ,eAAO,MAAM,wBAAwB,GAAI,iBAAiB,MAAM;;;EACJ,CAAA;AAE5D,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAgU5E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAUD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOnD;AAyBD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE5D;AAsBD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,GAAG,CAqB5E;AA6BD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAgBtD;AAk/JD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CA0B1G;AAED,eAAO,MAAM,iBAAiB,SAAU,CAAA;AACxC,eAAO,MAAM,uBAAuB,QAAkB,CAAA;AACtD,eAAO,MAAM,oBAAoB,aAgB/B,CAAA;AAEF,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,UAAU,CAaxH;AAmMD,wBAAsB,IAAI,CAAC,OAAO,WAAwB,iBAwgBzD"}
|