mcp-pacemaker 1.3.1 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +85 -27
- package/CONTRIBUTING.md +123 -47
- package/README.md +117 -62
- package/SECURITY.md +34 -24
- package/bin/mcp-bridge.mjs +91 -42
- package/bin/pooling-batch-scheduler.mjs +129 -0
- package/bin/pooling-batches.mjs +270 -0
- package/bin/pooling-config.mjs +413 -389
- package/bin/pooling-editor.mjs +128 -0
- package/bin/pooling-errors.mjs +23 -0
- package/bin/pooling-execution.mjs +19 -6
- package/bin/pooling-files.mjs +541 -0
- package/bin/pooling-trace.mjs +218 -0
- package/bin/pooling-writer.mjs +87 -14
- package/bin/prewarm.mjs +87 -6
- package/bin/windows/PoolingSecurityHelper.build.json +7 -4
- package/bin/windows/PoolingSecurityHelper.exe +0 -0
- package/bin/windows/src/PoolingNativeFiles.cs +678 -0
- package/bin/windows/src/PoolingSecurityHelper.cs +18 -5
- package/package.json +2 -2
- package/tools/windows-security-helper/README.md +146 -3
- package/tools/windows-security-helper/build.ps1 +2 -2
- package/ui/dist/assets/index-CaLPg0c-.css +1 -0
- package/ui/dist/assets/index-CgPruWYi.js +9 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/third-party-manifest.json +37 -19
- package/ui/dist/assets/index-CIy7uxfd.css +0 -1
- package/ui/dist/assets/index-CtaaQsB9.js +0 -8
|
@@ -10,16 +10,16 @@ public sealed class PoolingSecurityHelper
|
|
|
10
10
|
private readonly AccessControlSections sections =
|
|
11
11
|
AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group;
|
|
12
12
|
|
|
13
|
-
/// <summary>Inspects or preserves Windows file security.</summary>
|
|
14
|
-
/// <param name="args">One action
|
|
13
|
+
/// <summary>Inspects, stages, moves, or preserves Windows file security.</summary>
|
|
14
|
+
/// <param name="args">One action. File paths come from MCP_POOL environment variables.</param>
|
|
15
15
|
/// <returns>Zero on success, three on a source security conflict, or one on failure.</returns>
|
|
16
16
|
public static int Main(string[] args)
|
|
17
17
|
{
|
|
18
18
|
return new PoolingSecurityHelper().Run(args);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
/// <summary>Runs a
|
|
22
|
-
/// <param name="args">One
|
|
21
|
+
/// <summary>Runs a native file operation or a legacy security operation.</summary>
|
|
22
|
+
/// <param name="args">One of inspect-access, stage, move-no-replace, inspect, or copy.</param>
|
|
23
23
|
/// <returns>Zero on success, three on a source security conflict, or one on failure.</returns>
|
|
24
24
|
public int Run(string[] args)
|
|
25
25
|
{
|
|
@@ -31,6 +31,15 @@ public sealed class PoolingSecurityHelper
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
var sourcePath = this.RequiredEnvironment("MCP_POOL_SOURCE");
|
|
34
|
+
if (args[0] == "inspect-access" ||
|
|
35
|
+
args[0] == "stage" ||
|
|
36
|
+
args[0] == "move-no-replace")
|
|
37
|
+
{
|
|
38
|
+
var files = new PoolingNativeFiles();
|
|
39
|
+
Console.WriteLine(files.Run(args[0], sourcePath));
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
34
43
|
if (args[0] == "inspect")
|
|
35
44
|
{
|
|
36
45
|
Console.WriteLine(this.ReadSecurity(sourcePath).Fingerprint);
|
|
@@ -53,7 +62,11 @@ public sealed class PoolingSecurityHelper
|
|
|
53
62
|
Console.Error.WriteLine("MCPERR nativeError=" + nativeError.NativeErrorCode);
|
|
54
63
|
}
|
|
55
64
|
|
|
56
|
-
|
|
65
|
+
bool conflict = error is PoolingConflictException ||
|
|
66
|
+
(nativeError != null &&
|
|
67
|
+
(nativeError.NativeErrorCode == 80 ||
|
|
68
|
+
nativeError.NativeErrorCode == 183));
|
|
69
|
+
return conflict ? 3 : 1;
|
|
57
70
|
}
|
|
58
71
|
}
|
|
59
72
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-pacemaker",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Keeps your MCP servers alive across editor restarts, reboots, and token expiry — pluggable auth, cross-platform auto-start, one-command setup. Zero-dependency bridge; a small CLI for setup.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"compat:clean": "node tools/compatibility/prepare.mjs --clean",
|
|
57
57
|
"test:compat": "node --test --test-concurrency=1 test/compat/gates.test.mjs test/compat/cli-api.test.mjs",
|
|
58
58
|
"test:compat:browser": "node --test --test-concurrency=1 test/compat/browser.test.mjs",
|
|
59
|
-
"test": "node --test test/bridge.test.mjs test/bridge-controls.test.mjs test/kill-tree.test.mjs test/keepalive.test.mjs test/auth-token.test.mjs test/cold-start.test.mjs test/crash-report.test.mjs test/resume.test.mjs test/config-checks.test.mjs test/config-reload.test.mjs test/health.test.mjs test/cli.test.mjs test/logs.test.mjs test/prewarm.test.mjs test/pooling-config.test.mjs test/pooling-writer.test.mjs test/pooling-api.test.mjs test/pooling-deadlines.test.mjs test/pooling-queue-budget.test.mjs test/windows-security-helper.test.mjs test/tui.test.mjs test/warm-pool.test.mjs test/server-metrics.test.mjs test/admission.test.mjs test/shared-sessions.test.mjs test/shared-http.test.mjs test/cross-host.test.mjs test/suite-integrity.test.mjs test/consumer-package.test.mjs test/notices.test.mjs test/notices-runtime.test.mjs test/npm-publication.test.mjs test/npm-publication-proof.test.mjs test/update-check.test.mjs test/service-lifecycle.test.mjs test/service-supervisor.test.mjs test/npm-publication-gates.test.mjs test/publication-scanners-advisories.test.mjs test/publication-scanners.test.mjs test/service-replacement.test.mjs test/consumer-environment.test.mjs test/npm-publication-external.test.mjs test/npm-publication-
|
|
59
|
+
"test": "node --test test/bridge.test.mjs test/bridge-controls.test.mjs test/kill-tree.test.mjs test/keepalive.test.mjs test/auth-token.test.mjs test/cold-start.test.mjs test/crash-report.test.mjs test/resume.test.mjs test/config-checks.test.mjs test/config-reload.test.mjs test/health.test.mjs test/cli.test.mjs test/logs.test.mjs test/prewarm.test.mjs test/pooling-batch-scheduler.test.mjs test/pooling-batch-api.test.mjs test/pooling-batch-store.test.mjs test/pooling-batches.test.mjs test/pooling-stage-publication.test.mjs test/pooling-batch-startup.test.mjs test/pooling-postcommit-recovery.test.mjs test/pooling-config.test.mjs test/pooling-writer.test.mjs test/pooling-api.test.mjs test/pooling-deadlines.test.mjs test/pooling-queue-budget.test.mjs test/windows-security-helper.test.mjs test/windows-batch-security.test.mjs test/tui.test.mjs test/warm-pool.test.mjs test/server-metrics.test.mjs test/admission.test.mjs test/shared-sessions.test.mjs test/shared-http.test.mjs test/cross-host.test.mjs test/suite-integrity.test.mjs test/consumer-package.test.mjs test/notices.test.mjs test/notices-runtime.test.mjs test/npm-publication.test.mjs test/npm-publication-proof.test.mjs test/update-check.test.mjs test/service-lifecycle.test.mjs test/service-supervisor.test.mjs test/npm-publication-gates.test.mjs test/npm-publication-matrix.test.mjs test/publication-scanners-advisories.test.mjs test/publication-scanners.test.mjs test/service-replacement.test.mjs test/consumer-environment.test.mjs test/npm-publication-external.test.mjs test/npm-publication-peer.test.mjs test/publication-scanners-diagnostics.test.mjs test/publication-scanners-publication.test.mjs test/publication-scanners-paths.test.mjs test/npm-publication-local-enforcement.test.mjs test/publication-scanners-admission.test.mjs",
|
|
60
60
|
"notices:check": "node tools/third-party-notices/check.mjs",
|
|
61
61
|
"publication:source-gates": "node tools/npm-publication/source-gates.mjs",
|
|
62
62
|
"publication:artifact-gates": "node tools/npm-publication/artifact-gates.mjs",
|
|
@@ -16,7 +16,113 @@ AnyCPU uses the installed Framework's process architecture; native calls use
|
|
|
16
16
|
pointer-sized handles. Windows ARM64 execution has not been validated.
|
|
17
17
|
|
|
18
18
|
The Node caller must launch the package-relative executable directly (no shell)
|
|
19
|
-
with exactly one argument
|
|
19
|
+
with exactly one action argument. Paths are passed in the environment, never in
|
|
20
|
+
arguments. No action enables privileges.
|
|
21
|
+
|
|
22
|
+
### Batch file operations
|
|
23
|
+
|
|
24
|
+
| Action | Environment | UTF-8 stdin |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `inspect-access` | `MCP_POOL_SOURCE` | Unused |
|
|
27
|
+
| `stage` | `MCP_POOL_SOURCE`, `MCP_POOL_TEMP` (new destination) | `{"expected":{"identity":"…","revision":"…","security":"…"},"bytes":"<base64>"}` |
|
|
28
|
+
| `move-no-replace` | `MCP_POOL_SOURCE`, `MCP_POOL_TEMP` (destination) | `{"expected":{"identity":"…","revision":"…","security":"…"}}` |
|
|
29
|
+
|
|
30
|
+
Each successful action writes one JSON line with exactly
|
|
31
|
+
`{identity, revision, security, size}`. Pass only the first three fields back in
|
|
32
|
+
`expected`, not the complete result. `identity` is lowercase hexadecimal
|
|
33
|
+
`volumeSerial:fileIndexHighAndLow` (8 digits, colon, 16 digits). `revision` is
|
|
34
|
+
lowercase SHA256 of the complete bytes; `size` is their integer byte count.
|
|
35
|
+
`security` is a lowercase SHA256 comparison value, not a descriptor to log.
|
|
36
|
+
Custom config paths, including spaces and Unicode, use the same environment API.
|
|
37
|
+
|
|
38
|
+
All three actions require a disk file, no final-component reparse point, exactly
|
|
39
|
+
one hard link, and at most 1 MiB. Only Normal/Archive file attributes are supported;
|
|
40
|
+
read-only, compressed, encrypted, sparse, hidden, and other attributes fail closed.
|
|
41
|
+
The input envelope is limited to 1,400,000 bytes, strict UTF-8 and depth 8.
|
|
42
|
+
Edited bytes must be canonical base64 decoding to at most 1 MiB of UTF-8.
|
|
43
|
+
The helper does not parse the edited configuration; complete JSON validation and
|
|
44
|
+
allowed field edits remain the Node coordinator's responsibility.
|
|
45
|
+
|
|
46
|
+
Native `GetSecurityInfo` queries the held handle with `READ_CONTROL` sections
|
|
47
|
+
`0x1f7`. The new actions never call the legacy privileged audit reader.
|
|
48
|
+
The security hash frames `mcp-access-v1`, native owner SID, group SID,
|
|
49
|
+
DACL-protection bit, raw DACL, native label ACL, and attributes with `BinaryWriter`.
|
|
50
|
+
Byte arrays have signed Int32 lengths (`-1` for null). The discriminator is a
|
|
51
|
+
BinaryWriter UTF-8 string; protection and attributes are UInt32, all little-endian.
|
|
52
|
+
The DACL's `SE_DACL_AUTO_INHERITED` (`0x400`) bookkeeping is not hashed: Windows can
|
|
53
|
+
clear it while retaining the exact grants. Defaulted/auto-inherit-request and
|
|
54
|
+
other unsupported descriptor control state is refused. Audit control flags are
|
|
55
|
+
outside this non-audit fingerprint; label ACE bytes and flags remain exact.
|
|
56
|
+
|
|
57
|
+
Supported DACLs have non-null raw ACLs containing ordinary non-callback allow/deny
|
|
58
|
+
ACEs. Unsupported object/callback/policy ACEs fail closed. No label, or one
|
|
59
|
+
low/medium label with a nonzero mask composed only of NW/NR/NX (`1` through `7`),
|
|
60
|
+
is supported. Only the inherited label ACE flag is allowed. Other native non-audit
|
|
61
|
+
policy is refused.
|
|
62
|
+
|
|
63
|
+
`stage` opens the source with actual `FILE_READ_DATA | FILE_WRITE_DATA |
|
|
64
|
+
READ_CONTROL` access. Parent DeleteChild authority does not bypass source write
|
|
65
|
+
denial. The held source handle denies other data writers/deleters during staging.
|
|
66
|
+
Expected identity, bytes, and security are checked before creation and again
|
|
67
|
+
around writing. When the source owner equals the caller's user SID or current
|
|
68
|
+
token default owner, that owner is retained. Retaining an already assignable
|
|
69
|
+
default owner avoids an unnecessary ownership change across a version roundtrip.
|
|
70
|
+
Otherwise the caller must already obtain source `READ_CONTROL | WRITE_DAC` through
|
|
71
|
+
`ReOpenFile` on the same object; only simple allow DACLs without OWNER RIGHTS or
|
|
72
|
+
CREATOR special SIDs qualify. The candidate owner then becomes the creator.
|
|
73
|
+
The source group must remain exact or creation/verification fails.
|
|
74
|
+
|
|
75
|
+
The candidate uses native `CREATE_NEW` with `SECURITY_ATTRIBUTES` containing the
|
|
76
|
+
accepted owner/group/DACL/protection and supported labels **before any bytes**.
|
|
77
|
+
It never clones audit entries or sets SACL protection. A label-bearing creation
|
|
78
|
+
descriptor marks the SACL defaulted (`SE_SACL_DEFAULTED`); this lets Windows inherit
|
|
79
|
+
folder auditing while retaining the supplied mandatory label. The label remains
|
|
80
|
+
part of the exact empty-file verification. Windows inherits the
|
|
81
|
+
destination folder's auditing, including when the source protects its audit ACL.
|
|
82
|
+
File-specific auditing is not detected or promised to carry forward. The UI must
|
|
83
|
+
provide the agreed nonblocking audit-inheritance warning without claiming
|
|
84
|
+
custom rules were detected.
|
|
85
|
+
|
|
86
|
+
The empty candidate's security must equal the accepted fingerprint before
|
|
87
|
+
`WriteFile`. Bytes are written through that owned handle, flushed using
|
|
88
|
+
`FlushFileBuffers`, and then hashed and security-checked again. Security mismatch
|
|
89
|
+
before writing can leave an empty, securely created candidate. A later failure
|
|
90
|
+
can leave a partially/fully written candidate; callers must validate ownership
|
|
91
|
+
before cleanup, never blindly delete a destination on any failure.
|
|
92
|
+
|
|
93
|
+
`move-no-replace` holds a source handle with share-read/share-delete, denies data
|
|
94
|
+
writers, and checks identity/content/security before `MoveFileExW(..., 0)`.
|
|
95
|
+
It checks the destination and held object afterward and returns the moved
|
|
96
|
+
descriptor. Existing destinations are never overwritten. There is no exists-check
|
|
97
|
+
followed by an overwriting rename.
|
|
98
|
+
|
|
99
|
+
**Limits:** the rename API is path-based. Another actor with delete/rename or
|
|
100
|
+
directory/security authority can still swap the source path/ancestors between
|
|
101
|
+
validation and the move, or change security during an operation. Postchecks
|
|
102
|
+
detect some races but cannot undo them. This is not CAS, an atomic two-file
|
|
103
|
+
transaction, a power-loss guarantee, or a complete metadata clone. A failed move
|
|
104
|
+
postcheck can mean the move already happened; the coordinator must inspect
|
|
105
|
+
transaction state before recovery. Deadline/commit gating, rollback/recovery,
|
|
106
|
+
watcher coordination, and the missing-active interval belong to Node.
|
|
107
|
+
|
|
108
|
+
| Exit | New-action meaning |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| `0` | One descriptor JSON line; empty stderr |
|
|
111
|
+
| `3` | `PoolingConflictException` for an expected/state mismatch, or `Win32Exception` with native `80`/`183` for an existing destination |
|
|
112
|
+
| `1` | Policy, request, I/O, access, or other failure |
|
|
113
|
+
|
|
114
|
+
Failures have empty stdout. Stderr is exactly `MCPERR type=<exception type>` and,
|
|
115
|
+
for a `Win32Exception`, a second line `MCPERR nativeError=<integer>`. Relevant
|
|
116
|
+
types are `PoolingPolicyException` (unsupported or mismatched creation security),
|
|
117
|
+
`PoolingConflictException`, `Win32Exception`, `ArgumentException`,
|
|
118
|
+
`FormatException`, `DecoderFallbackException`, and `IOException`. Native `5` is
|
|
119
|
+
access denied; `32` is a sharing violation; `2`/`3` are missing path components.
|
|
120
|
+
No exception messages, paths, token information, config bytes, or descriptors are
|
|
121
|
+
printed. Legacy stale-security conflicts retain their empty-stderr behavior.
|
|
122
|
+
|
|
123
|
+
### Legacy compatibility
|
|
124
|
+
|
|
125
|
+
`inspect` and `copy` remain available with their existing contract:
|
|
20
126
|
|
|
21
127
|
| Environment variable | Used by | Value |
|
|
22
128
|
| --- | --- | --- |
|
|
@@ -71,8 +177,12 @@ From the repository root:
|
|
|
71
177
|
.\tools\windows-security-helper\build.ps1 -Verify -CompilerPath 'C:\path\to\Roslyn\csc.exe'
|
|
72
178
|
```
|
|
73
179
|
|
|
74
|
-
The build uses `/noconfig /nostdlib+` with explicit `mscorlib.dll
|
|
75
|
-
|
|
180
|
+
The build uses `/noconfig /nostdlib+` with explicit `mscorlib.dll`, `System.dll`,
|
|
181
|
+
`System.Core.dll`, and `System.Web.Extensions.dll` references. JSON serialization
|
|
182
|
+
uses .NET Framework's built-in `JavaScriptSerializer`, not an npm dependency.
|
|
183
|
+
Sources are `AssemblyInfo.cs`, `PoolingSecurityReader.cs`,
|
|
184
|
+
`PoolingSecurityHelper.cs`, and `PoolingNativeFiles.cs`, with fixed options,
|
|
185
|
+
LF-normalized UTF-8 source, and mapped source paths.
|
|
76
186
|
It compiles twice under different temporary paths and refuses unequal bytes.
|
|
77
187
|
Temporary build files are removed by explicit path. No `dotnet build`, runtime
|
|
78
188
|
Framework compiler, NuGet restore, or external binary is involved.
|
|
@@ -93,3 +203,36 @@ Audit-denial/delay test variants belong in disposable directories outside `bin/`
|
|
|
93
203
|
and must never be included in an npm tarball. Release validation must also run
|
|
94
204
|
the real package on supported Windows/Node versions and verify packaged file
|
|
95
205
|
inclusion. This build alone does not establish those platform results.
|
|
206
|
+
|
|
207
|
+
## Targeted tests
|
|
208
|
+
|
|
209
|
+
```powershell
|
|
210
|
+
npm exec --no --offline --package=node@20.20.2 -- node --test test/windows-security-helper.test.mjs test/windows-batch-security.test.mjs
|
|
211
|
+
npm exec --no --offline --package=node@22.23.2 -- node --test test/windows-security-helper.test.mjs test/windows-batch-security.test.mjs
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The real-helper tests cover descriptor stability, full-byte staging, exact 1 MiB
|
|
215
|
+
bounds, stale identity/revision/security, malformed/bounded input, held writers,
|
|
216
|
+
hard links, and both collision points. There are no successful-path mocks.
|
|
217
|
+
|
|
218
|
+
The isolated `TEST-SETUP` audit fixture requires an audit-reading oracle token
|
|
219
|
+
and an existing Explorer window. Otherwise that one test explicitly skips.
|
|
220
|
+
Explorer discovery first checks for a process in the current Windows session,
|
|
221
|
+
before COM access or fixture policy changes. An absent process/window produces
|
|
222
|
+
a structured capability reason; discovery exceptions and later worker failures
|
|
223
|
+
remain failures. A skip does not establish audit-oracle coverage: that requires
|
|
224
|
+
a non-skipped result on a machine with both prerequisites.
|
|
225
|
+
It launches the **same Node executable** through Explorer, verifies medium
|
|
226
|
+
integrity and absence of SeSecurityPrivilege, then runs the packaged helper
|
|
227
|
+
directly. The elevated oracle separately verifies raw DACL/group/labels,
|
|
228
|
+
owner rules, unchanged failed sources, and the retained original's complete
|
|
229
|
+
audit descriptor. The mixed audit case retains one explicit plus one inherited
|
|
230
|
+
source rule on `previous`; its new candidate has only the parent's inherited
|
|
231
|
+
rule. A protected audit source also permits folder inheritance on the candidate.
|
|
232
|
+
An incompatible inherited label must leave the candidate empty.
|
|
233
|
+
|
|
234
|
+
`test/fixtures/windows-batch-security/FixturePolicy.cs` is compiled only by
|
|
235
|
+
TEST-SETUP to establish synthetic raw DACLs and labels; it is not linked into,
|
|
236
|
+
loaded by, or shipped as a dependency of the production helper. All fixture
|
|
237
|
+
policy changes are confined to a fresh owned temporary directory, removed by
|
|
238
|
+
the test. No live files, tasks, bridge, or token privileges are changed.
|
|
@@ -70,8 +70,8 @@ $sourceDirectory = Join-Path $binaryDirectory 'src'
|
|
|
70
70
|
$binaryName = 'PoolingSecurityHelper.exe'
|
|
71
71
|
$binaryPath = Join-Path $binaryDirectory $binaryName
|
|
72
72
|
$metadataPath = Join-Path $binaryDirectory 'PoolingSecurityHelper.build.json'
|
|
73
|
-
$sourceNames = @('AssemblyInfo.cs', 'PoolingSecurityReader.cs', 'PoolingSecurityHelper.cs')
|
|
74
|
-
$referenceNames = @('mscorlib.dll', 'System.dll')
|
|
73
|
+
$sourceNames = @('AssemblyInfo.cs', 'PoolingSecurityReader.cs', 'PoolingSecurityHelper.cs', 'PoolingNativeFiles.cs')
|
|
74
|
+
$referenceNames = @('mscorlib.dll', 'System.dll', 'System.Core.dll', 'System.Web.Extensions.dll')
|
|
75
75
|
|
|
76
76
|
if (-not $CompilerPath) {
|
|
77
77
|
$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!visible{visibility:visible!important}.visible{visibility:visible}.sticky{position:sticky}.top-0{top:0}.z-10{z-index:10}.col-span-full{grid-column:1 / -1}.m-0{margin:0}.mx-auto{margin-left:auto;margin-right:auto}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-auto{margin-left:auto}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.flex{display:flex}.table{display:table}.grid{display:grid}.h-2\.5{height:.625rem}.max-h-52{max-height:13rem}.min-h-screen{min-height:100vh}.w-2\.5{width:.625rem}.w-40{width:10rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[260px\]{min-width:260px}.max-w-\[1160px\]{max-width:1160px}.max-w-\[1400px\]{max-width:1400px}.max-w-\[900px\]{max-width:900px}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.grid-cols-\[14px_160px_1fr\]{grid-template-columns:14px 160px 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-\[18px\]{gap:18px}.overflow-auto{overflow:auto}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-accent{--tw-border-opacity: 1;border-color:rgb(110 168 254 / var(--tw-border-opacity, 1))}.border-amber{--tw-border-opacity: 1;border-color:rgb(240 180 41 / var(--tw-border-opacity, 1))}.border-amber\/50{border-color:#f0b42980}.border-danger{--tw-border-opacity: 1;border-color:rgb(248 113 113 / var(--tw-border-opacity, 1))}.border-danger\/50{border-color:#f8717180}.border-line{--tw-border-opacity: 1;border-color:rgb(42 47 58 / var(--tw-border-opacity, 1))}.border-ok{--tw-border-opacity: 1;border-color:rgb(52 211 153 / var(--tw-border-opacity, 1))}.bg-accent\/10{background-color:#6ea8fe1a}.bg-amber{--tw-bg-opacity: 1;background-color:rgb(240 180 41 / var(--tw-bg-opacity, 1))}.bg-amber\/5{background-color:#f0b4290d}.bg-danger{--tw-bg-opacity: 1;background-color:rgb(248 113 113 / var(--tw-bg-opacity, 1))}.bg-danger\/5{background-color:#f871710d}.bg-ok{--tw-bg-opacity: 1;background-color:rgb(52 211 153 / var(--tw-bg-opacity, 1))}.bg-panel{--tw-bg-opacity: 1;background-color:rgb(23 26 35 / var(--tw-bg-opacity, 1))}.bg-panel2{--tw-bg-opacity: 1;background-color:rgb(30 34 45 / var(--tw-bg-opacity, 1))}.p-16{padding:4rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-3\.5{padding-top:.875rem;padding-bottom:.875rem}.pb-3{padding-bottom:.75rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.align-top{vertical-align:top}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.leading-relaxed{line-height:1.625}.text-accent{--tw-text-opacity: 1;color:rgb(110 168 254 / var(--tw-text-opacity, 1))}.text-amber{--tw-text-opacity: 1;color:rgb(240 180 41 / var(--tw-text-opacity, 1))}.text-danger{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-fg{--tw-text-opacity: 1;color:rgb(230 232 238 / var(--tw-text-opacity, 1))}.text-muted{--tw-text-opacity: 1;color:rgb(154 163 178 / var(--tw-text-opacity, 1))}.text-muted\/40{color:#9aa3b266}.text-muted\/60{color:#9aa3b299}.text-ok{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.shadow-\[0_0_6px\]{--tw-shadow: 0 0 6px;--tw-shadow-colored: 0 0 6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-ok{--tw-shadow-color: #34d399;--tw-shadow: var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.\[grid-template-columns\:repeat\(auto-fit\,minmax\(320px\,1fr\)\)\]{grid-template-columns:repeat(auto-fit,minmax(320px,1fr))}:root{color-scheme:dark}body{margin:0;background:#0f1117;color:#e6e8ee;font:14px/1.5 ui-sans-serif,system-ui,Segoe UI,Roboto,sans-serif}.hover\:border-accent:hover{--tw-border-opacity: 1;border-color:rgb(110 168 254 / var(--tw-border-opacity, 1))}.hover\:bg-accent\/20:hover{background-color:#6ea8fe33}.hover\:text-fg:hover{--tw-text-opacity: 1;color:rgb(230 232 238 / var(--tw-text-opacity, 1))}.focus\:border-accent:focus{--tw-border-opacity: 1;border-color:rgb(110 168 254 / var(--tw-border-opacity, 1))}.disabled\:cursor-default:disabled{cursor:default}.disabled\:opacity-40:disabled{opacity:.4}
|