codex-multi-auth 2.2.0 → 2.2.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/.codex-plugin/plugin.json +9 -0
- package/README.md +3 -2
- package/dist/lib/auth/device-auth.d.ts.map +1 -1
- package/dist/lib/auth/device-auth.js +15 -0
- package/dist/lib/auth/device-auth.js.map +1 -1
- package/dist/lib/auth/server.d.ts.map +1 -1
- package/dist/lib/auth/server.js +12 -7
- package/dist/lib/auth/server.js.map +1 -1
- package/dist/lib/capability-policy.d.ts.map +1 -1
- package/dist/lib/capability-policy.js +5 -0
- package/dist/lib/capability-policy.js.map +1 -1
- package/dist/lib/codex-cli/state.d.ts.map +1 -1
- package/dist/lib/codex-cli/state.js +28 -7
- package/dist/lib/codex-cli/state.js.map +1 -1
- package/dist/lib/codex-manager/commands/best.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/best.js +2 -2
- package/dist/lib/codex-manager/commands/best.js.map +1 -1
- package/dist/lib/codex-manager/commands/budget.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/budget.js +6 -0
- package/dist/lib/codex-manager/commands/budget.js.map +1 -1
- package/dist/lib/codex-manager/commands/forecast.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/forecast.js +9 -7
- package/dist/lib/codex-manager/commands/forecast.js.map +1 -1
- package/dist/lib/codex-manager/commands/integrations.js +2 -2
- package/dist/lib/codex-manager/commands/integrations.js.map +1 -1
- package/dist/lib/codex-manager/commands/models.js +3 -3
- package/dist/lib/codex-manager/commands/models.js.map +1 -1
- package/dist/lib/codex-manager/commands/report.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/report.js +9 -7
- package/dist/lib/codex-manager/commands/report.js.map +1 -1
- package/dist/lib/codex-manager/commands/workspace.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/workspace.js +12 -1
- package/dist/lib/codex-manager/commands/workspace.js.map +1 -1
- package/dist/lib/codex-manager/forecast-report-commands.d.ts.map +1 -1
- package/dist/lib/codex-manager/forecast-report-commands.js +11 -10
- package/dist/lib/codex-manager/forecast-report-commands.js.map +1 -1
- package/dist/lib/codex-manager/help.d.ts.map +1 -1
- package/dist/lib/codex-manager/help.js +7 -5
- package/dist/lib/codex-manager/help.js.map +1 -1
- package/dist/lib/codex-manager/repair-commands.js +7 -7
- package/dist/lib/codex-manager/repair-commands.js.map +1 -1
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +72 -32
- package/dist/lib/codex-manager.js.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +217 -13
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/forecast.d.ts +1 -0
- package/dist/lib/forecast.d.ts.map +1 -1
- package/dist/lib/forecast.js +25 -2
- package/dist/lib/forecast.js.map +1 -1
- package/dist/lib/integration-generators.d.ts.map +1 -1
- package/dist/lib/integration-generators.js +1 -1
- package/dist/lib/integration-generators.js.map +1 -1
- package/dist/lib/local-client-tokens.d.ts.map +1 -1
- package/dist/lib/local-client-tokens.js +117 -64
- package/dist/lib/local-client-tokens.js.map +1 -1
- package/dist/lib/model-capability-matrix.d.ts.map +1 -1
- package/dist/lib/model-capability-matrix.js +13 -10
- package/dist/lib/model-capability-matrix.js.map +1 -1
- package/dist/lib/policy/runtime-policy.d.ts.map +1 -1
- package/dist/lib/policy/runtime-policy.js +8 -0
- package/dist/lib/policy/runtime-policy.js.map +1 -1
- package/dist/lib/prompts/codex.d.ts +1 -1
- package/dist/lib/prompts/codex.d.ts.map +1 -1
- package/dist/lib/prompts/codex.js +4 -4
- package/dist/lib/prompts/codex.js.map +1 -1
- package/dist/lib/quota-cache.d.ts.map +1 -1
- package/dist/lib/quota-cache.js +16 -1
- package/dist/lib/quota-cache.js.map +1 -1
- package/dist/lib/quota-probe.d.ts.map +1 -1
- package/dist/lib/quota-probe.js +2 -1
- package/dist/lib/quota-probe.js.map +1 -1
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +3 -0
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/request/helpers/model-map.d.ts +1 -0
- package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
- package/dist/lib/request/helpers/model-map.js +10 -0
- package/dist/lib/request/helpers/model-map.js.map +1 -1
- package/dist/lib/routing-mutex.d.ts +12 -0
- package/dist/lib/routing-mutex.d.ts.map +1 -1
- package/dist/lib/routing-mutex.js +38 -1
- package/dist/lib/routing-mutex.js.map +1 -1
- package/dist/lib/runtime/quota-probe.d.ts.map +1 -1
- package/dist/lib/runtime/quota-probe.js +2 -5
- package/dist/lib/runtime/quota-probe.js.map +1 -1
- package/dist/lib/runtime-rotation-proxy.d.ts +23 -1
- package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
- package/dist/lib/runtime-rotation-proxy.js +127 -11
- package/dist/lib/runtime-rotation-proxy.js.map +1 -1
- package/dist/lib/storage/account-persistence.d.ts.map +1 -1
- package/dist/lib/storage/account-persistence.js +11 -1
- package/dist/lib/storage/account-persistence.js.map +1 -1
- package/dist/lib/storage/record-utils.d.ts.map +1 -1
- package/dist/lib/storage/record-utils.js +6 -1
- package/dist/lib/storage/record-utils.js.map +1 -1
- package/dist/lib/storage/transactions.d.ts +8 -0
- package/dist/lib/storage/transactions.d.ts.map +1 -1
- package/dist/lib/storage/transactions.js +14 -1
- package/dist/lib/storage/transactions.js.map +1 -1
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +46 -9
- package/dist/lib/storage.js.map +1 -1
- package/package.json +4 -3
- package/scripts/codex-bin-resolver.js +28 -1
- package/scripts/mcodex.js +367 -0
- package/scripts/mcodex +0 -117
|
@@ -46,7 +46,7 @@ import { DEFAULT_DASHBOARD_DISPLAY_SETTINGS, loadDashboardDisplaySettings, } fro
|
|
|
46
46
|
import { evaluateForecastAccounts, recommendForecastAccount, summarizeForecast, } from "./forecast.js";
|
|
47
47
|
import { createLogger } from "./logger.js";
|
|
48
48
|
import { MODEL_FAMILIES } from "./prompts/codex.js";
|
|
49
|
-
import { getModelCapabilities, getModelProfile, resolveNormalizedModel, } from "./request/helpers/model-map.js";
|
|
49
|
+
import { DEFAULT_MODEL, getModelCapabilities, getModelProfile, resolveNormalizedModel, } from "./request/helpers/model-map.js";
|
|
50
50
|
import { formatRateLimitEntry as formatAccountRateLimitEntry, getRateLimitResetTimeForFamily, resolveActiveIndex, } from "./runtime/account-status.js";
|
|
51
51
|
import { buildQuotaEmailFallbackState, hasSafeQuotaEmailFallback, hasUniqueQuotaAccountId, isQuotaCacheEntryExhausted, normalizeQuotaAccountId, normalizeQuotaEmail, quotaLeftPercentFromUsed, } from "./quota-readiness.js";
|
|
52
52
|
import { loadQuotaCache, saveQuotaCache, } from "./quota-cache.js";
|
|
@@ -218,7 +218,7 @@ function styleQuotaSummary(summary) {
|
|
|
218
218
|
return stylePromptText(segment, "muted");
|
|
219
219
|
}
|
|
220
220
|
const windowLabel = match[1] ?? "";
|
|
221
|
-
const leftPercent = Number.parseInt(match[2] ?? "", 10);
|
|
221
|
+
const leftPercent = Math.max(0, Math.min(100, Number.parseInt(match[2] ?? "", 10)));
|
|
222
222
|
if (!Number.isFinite(leftPercent)) {
|
|
223
223
|
return stylePromptText(segment, "muted");
|
|
224
224
|
}
|
|
@@ -239,9 +239,14 @@ export function styleAccountDetailText(detail, fallbackTone = "muted") {
|
|
|
239
239
|
const prefix = (quotaMatch[1] ?? "").trim();
|
|
240
240
|
const quota = (quotaMatch[2] ?? "").trim();
|
|
241
241
|
const suffix = (quotaMatch[3] ?? "").trim();
|
|
242
|
-
|
|
242
|
+
// danger wins across the WHOLE detail: a failure keyword anywhere — even
|
|
243
|
+
// trapped inside the (…%) quota segment, e.g.
|
|
244
|
+
// "signed in and working (live check failed: … 0%)" — must keep the prefix
|
|
245
|
+
// red, never let a "working"/"ok" prefix render green over a real failure.
|
|
246
|
+
const detailHasFailure = /failed|error|rate-limited/i.test(compact);
|
|
247
|
+
const prefixTone = detailHasFailure
|
|
243
248
|
? "danger"
|
|
244
|
-
:
|
|
249
|
+
: /\b(ok|working|succeeded|valid)\b/i.test(prefix)
|
|
245
250
|
? "success"
|
|
246
251
|
: fallbackTone;
|
|
247
252
|
const suffixTone =
|
|
@@ -267,7 +272,7 @@ export function styleAccountDetailText(detail, fallbackTone = "muted") {
|
|
|
267
272
|
return stylePromptText(compact, "danger");
|
|
268
273
|
if (/re-login|stale|warning|fallback|unavailable|not available/i.test(compact))
|
|
269
274
|
return stylePromptText(compact, "warning");
|
|
270
|
-
if (
|
|
275
|
+
if (/\b(ok|working|succeeded|valid)\b/i.test(compact))
|
|
271
276
|
return stylePromptText(compact, "success");
|
|
272
277
|
return stylePromptText(compact, fallbackTone);
|
|
273
278
|
}
|
|
@@ -441,7 +446,7 @@ function getPersistedQuotaViewForAccount(cache, account, accounts, now, emailFal
|
|
|
441
446
|
return {
|
|
442
447
|
updatedAt: cachedEntry?.updatedAt ?? now,
|
|
443
448
|
status: 429,
|
|
444
|
-
model: cachedEntry?.model ??
|
|
449
|
+
model: cachedEntry?.model ?? DEFAULT_MODEL,
|
|
445
450
|
planType: cachedEntry?.planType,
|
|
446
451
|
primary: {
|
|
447
452
|
...cachedEntry?.primary,
|
|
@@ -509,7 +514,8 @@ function pruneUnsafeQuotaEmailCacheEntry(cache, email, accounts, emailFallbackSt
|
|
|
509
514
|
return true;
|
|
510
515
|
}
|
|
511
516
|
const DEFAULT_MENU_QUOTA_REFRESH_TTL_MS = 5 * 60_000;
|
|
512
|
-
const
|
|
517
|
+
const DEFAULT_LIVE_PROBE_MODEL = DEFAULT_MODEL;
|
|
518
|
+
const MENU_QUOTA_REFRESH_MODEL = DEFAULT_LIVE_PROBE_MODEL;
|
|
513
519
|
function resolveMenuQuotaProbeInput(account, cache, maxAgeMs, now, accounts, emailFallbackState = buildQuotaEmailFallbackState(accounts)) {
|
|
514
520
|
if (account.enabled === false)
|
|
515
521
|
return null;
|
|
@@ -1581,7 +1587,7 @@ async function syncSelectionToCodex(tokens) {
|
|
|
1581
1587
|
async function runHealthCheck(options = {}) {
|
|
1582
1588
|
const forceRefresh = options.forceRefresh === true;
|
|
1583
1589
|
const liveProbe = options.liveProbe === true;
|
|
1584
|
-
const probeModel = options.model?.trim() ||
|
|
1590
|
+
const probeModel = options.model?.trim() || DEFAULT_LIVE_PROBE_MODEL;
|
|
1585
1591
|
const modelInspection = inspectRequestedModel(probeModel);
|
|
1586
1592
|
const display = options.display ?? DEFAULT_DASHBOARD_DISPLAY_SETTINGS;
|
|
1587
1593
|
const quotaCache = liveProbe ? await loadQuotaCache() : null;
|
|
@@ -1600,6 +1606,8 @@ async function runHealthCheck(options = {}) {
|
|
|
1600
1606
|
let ok = 0;
|
|
1601
1607
|
let failed = 0;
|
|
1602
1608
|
let warnings = 0;
|
|
1609
|
+
let codexAvailable = 0;
|
|
1610
|
+
let signedInOnly = 0;
|
|
1603
1611
|
const activeIndex = resolveActiveIndex(storage, "codex");
|
|
1604
1612
|
let activeAccountRefreshed = false;
|
|
1605
1613
|
const now = Date.now();
|
|
@@ -1625,6 +1633,7 @@ async function runHealthCheck(options = {}) {
|
|
|
1625
1633
|
activeAccountRefreshed = true;
|
|
1626
1634
|
}
|
|
1627
1635
|
let healthDetail = "signed in and working";
|
|
1636
|
+
let healthTone = "success";
|
|
1628
1637
|
if (liveProbe) {
|
|
1629
1638
|
const currentAccessToken = account.accessToken;
|
|
1630
1639
|
const probeAccountId = currentAccessToken
|
|
@@ -1632,8 +1641,10 @@ async function runHealthCheck(options = {}) {
|
|
|
1632
1641
|
: undefined;
|
|
1633
1642
|
if (!probeAccountId || !currentAccessToken) {
|
|
1634
1643
|
warnings += 1;
|
|
1644
|
+
signedInOnly += 1;
|
|
1645
|
+
healthTone = "warning";
|
|
1635
1646
|
healthDetail =
|
|
1636
|
-
"signed in
|
|
1647
|
+
"signed in (live check skipped: missing account ID)";
|
|
1637
1648
|
}
|
|
1638
1649
|
else {
|
|
1639
1650
|
try {
|
|
@@ -1647,16 +1658,19 @@ async function runHealthCheck(options = {}) {
|
|
|
1647
1658
|
updateQuotaCacheForAccount(workingQuotaCache, account, snapshot, storage.accounts, quotaEmailFallbackState ?? undefined) || quotaCacheChanged;
|
|
1648
1659
|
}
|
|
1649
1660
|
healthDetail = formatQuotaSnapshotForDashboard(snapshot, display);
|
|
1661
|
+
codexAvailable += 1;
|
|
1650
1662
|
}
|
|
1651
1663
|
catch (error) {
|
|
1652
1664
|
warnings += 1;
|
|
1665
|
+
signedInOnly += 1;
|
|
1666
|
+
healthTone = "warning";
|
|
1653
1667
|
if (isCodexUnavailableError(error)) {
|
|
1654
1668
|
healthDetail =
|
|
1655
|
-
`signed in
|
|
1669
|
+
`signed in; ${CODEX_UNAVAILABLE_PROBE_NOTE}`;
|
|
1656
1670
|
}
|
|
1657
1671
|
else {
|
|
1658
1672
|
const message = normalizeFailureDetail(error instanceof Error ? error.message : String(error), undefined);
|
|
1659
|
-
healthDetail = `signed in
|
|
1673
|
+
healthDetail = `signed in (live check failed: ${message})`;
|
|
1660
1674
|
}
|
|
1661
1675
|
}
|
|
1662
1676
|
}
|
|
@@ -1666,7 +1680,8 @@ async function runHealthCheck(options = {}) {
|
|
|
1666
1680
|
}
|
|
1667
1681
|
ok += 1;
|
|
1668
1682
|
if (display.showPerAccountRows) {
|
|
1669
|
-
|
|
1683
|
+
const healthMarker = healthTone === "success" ? "✓" : "!";
|
|
1684
|
+
console.log(` ${stylePromptText(healthMarker, healthTone)} ${labelText} ${stylePromptText("|", "muted")} ${styleAccountDetailText(healthDetail, healthTone)}`);
|
|
1670
1685
|
}
|
|
1671
1686
|
continue;
|
|
1672
1687
|
}
|
|
@@ -1712,12 +1727,15 @@ async function runHealthCheck(options = {}) {
|
|
|
1712
1727
|
}
|
|
1713
1728
|
ok += 1;
|
|
1714
1729
|
let healthyMessage = "working now";
|
|
1730
|
+
let healthyTone = "success";
|
|
1715
1731
|
if (liveProbe) {
|
|
1716
1732
|
const probeAccountId = account.accountId ?? tokenAccountId;
|
|
1717
1733
|
if (!probeAccountId) {
|
|
1718
1734
|
warnings += 1;
|
|
1735
|
+
signedInOnly += 1;
|
|
1736
|
+
healthyTone = "warning";
|
|
1719
1737
|
healthyMessage =
|
|
1720
|
-
"
|
|
1738
|
+
"signed in (live check skipped: missing account ID)";
|
|
1721
1739
|
}
|
|
1722
1740
|
else {
|
|
1723
1741
|
try {
|
|
@@ -1731,28 +1749,35 @@ async function runHealthCheck(options = {}) {
|
|
|
1731
1749
|
updateQuotaCacheForAccount(workingQuotaCache, account, snapshot, storage.accounts, quotaEmailFallbackState ?? undefined) || quotaCacheChanged;
|
|
1732
1750
|
}
|
|
1733
1751
|
healthyMessage = formatQuotaSnapshotForDashboard(snapshot, display);
|
|
1752
|
+
codexAvailable += 1;
|
|
1734
1753
|
}
|
|
1735
1754
|
catch (error) {
|
|
1736
1755
|
warnings += 1;
|
|
1756
|
+
signedInOnly += 1;
|
|
1757
|
+
healthyTone = "warning";
|
|
1737
1758
|
if (isCodexUnavailableError(error)) {
|
|
1738
1759
|
healthyMessage =
|
|
1739
|
-
`
|
|
1760
|
+
`signed in; ${CODEX_UNAVAILABLE_PROBE_NOTE}`;
|
|
1740
1761
|
}
|
|
1741
1762
|
else {
|
|
1742
1763
|
const message = normalizeFailureDetail(error instanceof Error ? error.message : String(error), undefined);
|
|
1743
|
-
healthyMessage = `
|
|
1764
|
+
healthyMessage = `signed in (live check failed: ${message})`;
|
|
1744
1765
|
}
|
|
1745
1766
|
}
|
|
1746
1767
|
}
|
|
1747
1768
|
}
|
|
1748
1769
|
if (display.showPerAccountRows) {
|
|
1749
|
-
|
|
1770
|
+
const healthyMarker = healthyTone === "success" ? "✓" : "!";
|
|
1771
|
+
console.log(` ${stylePromptText(healthyMarker, healthyTone)} ${labelText} ${stylePromptText("|", "muted")} ${styleAccountDetailText(healthyMessage, healthyTone)}`);
|
|
1750
1772
|
}
|
|
1751
1773
|
}
|
|
1752
1774
|
else {
|
|
1753
1775
|
const detail = normalizeFailureDetail(result.message, result.reason);
|
|
1754
1776
|
if (sessionLikelyValid) {
|
|
1755
1777
|
warnings += 1;
|
|
1778
|
+
if (liveProbe) {
|
|
1779
|
+
signedInOnly += 1;
|
|
1780
|
+
}
|
|
1756
1781
|
if (display.showPerAccountRows) {
|
|
1757
1782
|
console.log(` ${stylePromptText("!", "warning")} ${labelText} ${stylePromptText("|", "muted")} ${stylePromptText(`refresh failed (${detail}) but this account still works right now`, "warning")}`);
|
|
1758
1783
|
}
|
|
@@ -1796,17 +1821,32 @@ async function runHealthCheck(options = {}) {
|
|
|
1796
1821
|
}
|
|
1797
1822
|
}
|
|
1798
1823
|
console.log("");
|
|
1799
|
-
console.log(formatResultSummary(
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1824
|
+
console.log(formatResultSummary(liveProbe
|
|
1825
|
+
? [
|
|
1826
|
+
{
|
|
1827
|
+
text: `${codexAvailable} Codex available`,
|
|
1828
|
+
tone: codexAvailable > 0 ? "success" : "muted",
|
|
1829
|
+
},
|
|
1830
|
+
{
|
|
1831
|
+
text: `${signedInOnly} signed in only`,
|
|
1832
|
+
tone: signedInOnly > 0 ? "warning" : "muted",
|
|
1833
|
+
},
|
|
1834
|
+
{
|
|
1835
|
+
text: `${failed} need re-login`,
|
|
1836
|
+
tone: failed > 0 ? "danger" : "muted",
|
|
1837
|
+
},
|
|
1838
|
+
]
|
|
1839
|
+
: [
|
|
1840
|
+
{ text: `${ok} working`, tone: "success" },
|
|
1841
|
+
{
|
|
1842
|
+
text: `${failed} need re-login`,
|
|
1843
|
+
tone: failed > 0 ? "danger" : "muted",
|
|
1844
|
+
},
|
|
1845
|
+
{
|
|
1846
|
+
text: `${warnings} warning${warnings === 1 ? "" : "s"}`,
|
|
1847
|
+
tone: warnings > 0 ? "warning" : "muted",
|
|
1848
|
+
},
|
|
1849
|
+
]));
|
|
1810
1850
|
}
|
|
1811
1851
|
function printBestUsage() {
|
|
1812
1852
|
console.log([
|
|
@@ -1816,7 +1856,7 @@ function printBestUsage() {
|
|
|
1816
1856
|
"Options:",
|
|
1817
1857
|
" --live, -l Probe live quota headers via Codex backend before switching",
|
|
1818
1858
|
" --json, -j Print machine-readable JSON output",
|
|
1819
|
-
|
|
1859
|
+
` --model, -m Probe model for live mode (default: ${DEFAULT_LIVE_PROBE_MODEL})`,
|
|
1820
1860
|
"",
|
|
1821
1861
|
"Behavior:",
|
|
1822
1862
|
" - Chooses the healthiest account using forecast scoring",
|
|
@@ -1827,7 +1867,7 @@ function parseBestArgs(args) {
|
|
|
1827
1867
|
const options = {
|
|
1828
1868
|
live: false,
|
|
1829
1869
|
json: false,
|
|
1830
|
-
model:
|
|
1870
|
+
model: DEFAULT_LIVE_PROBE_MODEL,
|
|
1831
1871
|
modelProvided: false,
|
|
1832
1872
|
};
|
|
1833
1873
|
for (let i = 0; i < args.length; i += 1) {
|
|
@@ -1843,8 +1883,8 @@ function parseBestArgs(args) {
|
|
|
1843
1883
|
continue;
|
|
1844
1884
|
}
|
|
1845
1885
|
if (arg === "--model" || arg === "-m") {
|
|
1846
|
-
const value = args[i + 1];
|
|
1847
|
-
if (!value) {
|
|
1886
|
+
const value = args[i + 1]?.trim();
|
|
1887
|
+
if (!value || value.startsWith("-")) {
|
|
1848
1888
|
return { ok: false, message: "Missing value for --model" };
|
|
1849
1889
|
}
|
|
1850
1890
|
options.model = value;
|
|
@@ -1854,7 +1894,7 @@ function parseBestArgs(args) {
|
|
|
1854
1894
|
}
|
|
1855
1895
|
if (arg.startsWith("--model=")) {
|
|
1856
1896
|
const value = arg.slice("--model=".length).trim();
|
|
1857
|
-
if (!value) {
|
|
1897
|
+
if (!value || value.startsWith("-")) {
|
|
1858
1898
|
return { ok: false, message: "Missing value for --model" };
|
|
1859
1899
|
}
|
|
1860
1900
|
options.model = value;
|