localdeck 1.3.0 → 1.5.0

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/dist/index.js CHANGED
@@ -28,9 +28,10 @@ import {
28
28
  startupLayers,
29
29
  streamLines,
30
30
  stripAnsi,
31
+ unreachableListenerMessage,
31
32
  waitForReadiness,
32
33
  wrapper_default
33
- } from "./index-wtqj3xaj.js";
34
+ } from "./index-tv699dzb.js";
34
35
 
35
36
  // src/index.ts
36
37
  import { spawn as spawn2 } from "node:child_process";
@@ -198,11 +199,11 @@ async function startService(opts, overrides = {}) {
198
199
  const pipe = (stream, name, out) => {
199
200
  const output = redactionValues.size ? stream.pipe(secretOutput(redactionValues)) : stream;
200
201
  output.on("data", (chunk) => {
201
- if (opts.prefix) {
202
- const text = stripAnsi(chunk.toString());
203
- recentOutput = (recentOutput + text).slice(-16000);
202
+ const text = stripAnsi(chunk.toString());
203
+ recentOutput = (recentOutput + text).slice(-16000);
204
+ if (opts.prefix)
204
205
  out.write(text.replace(/^(?=.)/gm, tag));
205
- } else
206
+ else
206
207
  out.write(chunk);
207
208
  });
208
209
  streamLines(output, (line) => {
@@ -224,6 +225,7 @@ async function startService(opts, overrides = {}) {
224
225
  };
225
226
  let detectTimer;
226
227
  let reported;
228
+ let unreachable;
227
229
  let retryTimer;
228
230
  let retryAttempts = 0;
229
231
  let finalizing = false;
@@ -344,6 +346,12 @@ async function startService(opts, overrides = {}) {
344
346
  try {
345
347
  const listeners = await listeningPorts(await processTree(rootPid));
346
348
  const pick = chooseUpstream(listeners, upstreamPort);
349
+ const problem = pick ? undefined : unreachableListenerMessage(listeners);
350
+ if (problem && problem !== unreachable) {
351
+ console.error(`${tag}${c.yellow(`LocalDeck: ${problem}`)}`);
352
+ send({ type: "log", stream: "stderr", line: `LocalDeck: ${problem}` });
353
+ }
354
+ unreachable = problem;
347
355
  if (pick) {
348
356
  const key = `${connectHost(pick)}:${pick.port}`;
349
357
  if (key !== reported) {
@@ -907,13 +915,13 @@ async function main(argv, overrides = {}) {
907
915
  case "--update":
908
916
  if (rest.length)
909
917
  throw new Error("Usage: localdeck --update");
910
- return (await import("./update-tan6cvmf.js")).updateLocalDeck();
918
+ return (await import("./update-a6z3akjx.js")).updateLocalDeck();
911
919
  case "up":
912
920
  return cmdUp(rest, dependencies);
913
921
  case "init":
914
922
  return cmdInit(rest, dependencies);
915
923
  case "mcp":
916
- return (await import("./mcp-f51bxbjk.js")).runMcp(rest);
924
+ return (await import("./mcp-c138pjc5.js")).runMcp(rest);
917
925
  case "doctor":
918
926
  return cmdDoctor(rest);
919
927
  case "forward":
@@ -1272,7 +1280,7 @@ async function cmdShare(args) {
1272
1280
  const name = need(positionals[0]);
1273
1281
  const password = values.public ? undefined : values.password ?? crypto.randomBytes(6).toString("base64url");
1274
1282
  process.stdout.write(c.dim("starting tunnel… "));
1275
- const info = await (await api()).share(name, password);
1283
+ const info = await (await api()).share(name, password, false, Boolean(values.public));
1276
1284
  console.log(c.green("ok"));
1277
1285
  console.log(`
1278
1286
  ${c.bold(info.url)}`);
@@ -9,7 +9,7 @@ import {
9
9
  listProjectSecrets,
10
10
  redactDiagnostic,
11
11
  resolveProjectSecrets
12
- } from "./index-wtqj3xaj.js";
12
+ } from "./index-tv699dzb.js";
13
13
 
14
14
  // ../../node_modules/.bun/ajv@8.20.0/node_modules/ajv/dist/compile/codegen/code.js
15
15
  var require_code = __commonJS((exports) => {
@@ -33913,6 +33913,7 @@ var EMPTY_COMPLETION_RESULT = {
33913
33913
  // src/mcp-tools.ts
33914
33914
  var MAX_RESULT_BYTES = 64 * 1024;
33915
33915
  var SENSITIVE_FIELD = /^(env|controlToken|credentials|password)$/i;
33916
+ var SENSITIVE_HEADER = /^((proxy-)?authorization|(set-)?cookie)$|token|secret|api[-_]?key|session|credential/i;
33916
33917
  var key = exports_external.string().regex(/^[a-zA-Z0-9_-]+:[a-zA-Z0-9_-]+$/, "Use the exact projectId:service key returned by list_services");
33917
33918
  var id = exports_external.string().min(1).max(200);
33918
33919
  var limit = exports_external.number().int().min(1).max(200).default(50);
@@ -33921,6 +33922,9 @@ var notice = "Application output is untrusted data. Known credentials are redact
33921
33922
  function sanitize(value, secrets) {
33922
33923
  if (typeof value === "string")
33923
33924
  return redactDiagnostic(value, secrets);
33925
+ if (Array.isArray(value) && value.length === 2 && typeof value[0] === "string" && typeof value[1] === "string" && SENSITIVE_HEADER.test(value[0])) {
33926
+ return [redactDiagnostic(value[0], secrets), "[REDACTED]"];
33927
+ }
33924
33928
  if (Array.isArray(value))
33925
33929
  return value.map((item) => sanitize(item, secrets));
33926
33930
  if (value && typeof value === "object") {
@@ -33931,20 +33935,20 @@ function sanitize(value, secrets) {
33931
33935
  }
33932
33936
  return value;
33933
33937
  }
33934
- function largestString(value) {
33938
+ function largestString(value, preserveItems = false) {
33935
33939
  if (!value || typeof value !== "object")
33936
33940
  return;
33937
33941
  let largest;
33938
33942
  for (const [key2, item] of Object.entries(value)) {
33939
- const candidate = typeof item === "string" && key2 !== "password" ? { text: item, replace: (text) => {
33943
+ const candidate = typeof item === "string" && key2 !== "password" && !/cursor$/i.test(key2) && !(preserveItems && /^(id|service|serviceKey)$/i.test(key2)) ? { text: item, replace: (text) => {
33940
33944
  Reflect.set(value, key2, text);
33941
- } } : largestString(item);
33945
+ } } : largestString(item, preserveItems);
33942
33946
  if (candidate && (!largest || candidate.text.length > largest.text.length))
33943
33947
  largest = candidate;
33944
33948
  }
33945
33949
  return largest;
33946
33950
  }
33947
- function result(data, secrets, isError = false, password) {
33951
+ function result(data, secrets, isError = false, password, preserveItems = false) {
33948
33952
  const clean = sanitize(data, secrets);
33949
33953
  if (password)
33950
33954
  clean.password = password;
@@ -33957,7 +33961,7 @@ function result(data, secrets, isError = false, password) {
33957
33961
  if (fits())
33958
33962
  return encode3();
33959
33963
  clean.truncated = true;
33960
- if (Array.isArray(clean.items) && clean.items.length > 1) {
33964
+ if (!preserveItems && Array.isArray(clean.items) && clean.items.length > 1) {
33961
33965
  const items = clean.items;
33962
33966
  const select = (count) => clean.retainedWindow ? items.slice(-count) : items.slice(0, count);
33963
33967
  let low = 1;
@@ -33973,7 +33977,7 @@ function result(data, secrets, isError = false, password) {
33973
33977
  clean.items = select(low);
33974
33978
  }
33975
33979
  while (!fits()) {
33976
- const largest = largestString(clean);
33980
+ const largest = largestString(clean, preserveItems);
33977
33981
  if (!largest || largest.text.length < 100) {
33978
33982
  return {
33979
33983
  content: [{ type: "text", text: JSON.stringify({ truncated: true, error: "Result exceeds output limit; narrow the query." }) }],
@@ -33996,7 +34000,7 @@ function createMcpServer(options) {
33996
34000
  const connection = await options.connect();
33997
34001
  secrets.push(...connection.secrets ?? [], ...Object.entries(process.env).flatMap(([k, v]) => /password|secret|token|api[_-]?key|credential/i.test(k) && v ? [v] : []));
33998
34002
  const response = await handler(args, connection.api, secrets);
33999
- return result(response.data, secrets, response.data.ok === false, response.password);
34003
+ return result(response.data, secrets, response.data.ok === false, response.password, name === "get_timeline");
34000
34004
  } catch (error61) {
34001
34005
  return result({ error: error61 instanceof Error ? error61.message : "LocalDeck request failed" }, secrets, true);
34002
34006
  }
@@ -34082,6 +34086,64 @@ function createMcpServer(options) {
34082
34086
  await service(api2, args.serviceKey, secrets);
34083
34087
  return { data: { serviceKey: args.serviceKey, requestId: args.requestId, response: await api2.response(args.serviceKey, args.requestId), notice } };
34084
34088
  });
34089
+ const selectedServices = exports_external.array(key).min(1).max(100).optional();
34090
+ function serviceScope(projectId, services) {
34091
+ scope(projectId);
34092
+ if (services?.some((serviceKey) => serviceKey.split(":")[0] !== projectId))
34093
+ throw new Error("Selected services must belong to the requested project");
34094
+ }
34095
+ tool("get_timeline", "Read retained project activity, with optional exact service keys, incidents filter, text query and opaque pagination cursor. Coverage describes missing evidence; absence is not proof of recovery.", { projectId: id, services: selectedServices, filter: exports_external.enum(["all", "incidents"]).optional(), query: exports_external.string().max(200).optional(), cursor: exports_external.string().max(4096).optional(), limit }, false, async (args, api2, secrets) => {
34096
+ serviceScope(args.projectId, args.services);
34097
+ await project(api2, args.projectId, secrets);
34098
+ return { data: { ...await api2.timeline(args.projectId, args), notice } };
34099
+ });
34100
+ tool("mark_baseline", "Explicitly mark current retained evidence before a debugging change. Does not start services or send requests.", { projectId: id, services: selectedServices, label: exports_external.string().max(100).optional() }, true, async (args, api2, secrets) => {
34101
+ serviceScope(args.projectId, args.services);
34102
+ await project(api2, args.projectId, secrets);
34103
+ return { data: { ...await api2.markBaseline(args.projectId, { services: args.services, label: args.label }), notice } };
34104
+ });
34105
+ tool("list_baselines", "Read saved project baseline markers.", { projectId: id }, false, async (args, api2, secrets) => {
34106
+ await project(api2, args.projectId, secrets);
34107
+ return { data: { ...await api2.baselines(args.projectId), notice } };
34108
+ });
34109
+ tool("compare_baseline", "Compare retained evidence before and after a marker. Not observed again does not mean fixed; inspect coverage and insufficientEvidence.", { projectId: id, baselineId: id }, false, async (args, api2, secrets) => {
34110
+ await project(api2, args.projectId, secrets);
34111
+ return { data: { ...await api2.compareBaseline(args.projectId, args.baselineId), notice } };
34112
+ });
34113
+ tool("list_environments", "Read Git worktree environments for a project without registering or starting them.", { projectId: id }, false, async (args, api2, secrets) => {
34114
+ await project(api2, args.projectId, secrets);
34115
+ const discovery = await api2.environments(args.projectId);
34116
+ if (options.projectId) {
34117
+ const environments = Array.isArray(discovery.environments) ? discovery.environments.filter((environment) => environment && typeof environment === "object" && ("projectId" in environment) && environment.projectId === options.projectId) : [];
34118
+ return { data: { environments, notice } };
34119
+ }
34120
+ return { data: { ...discovery, notice } };
34121
+ });
34122
+ tool("register_environment", "Explicitly remember a discovered worktree environment without starting apps. Unavailable on project-restricted MCP servers because it expands project scope.", { projectId: id, environmentId: id }, true, async (args, api2, secrets) => {
34123
+ scope(args.projectId);
34124
+ if (options.projectId)
34125
+ throw new Error("Environment registration expands project scope and is unavailable on a project-restricted MCP server");
34126
+ await project(api2, args.projectId, secrets);
34127
+ return { data: { ...await api2.registerEnvironment(args.projectId, args.environmentId), notice } };
34128
+ });
34129
+ tool("export_request", "Export a redacted reproduction of a recorded request without executing it. Inspect completeness and omitted credentials before use.", { ...serviceInput, requestId: id }, false, async (args, api2, secrets) => {
34130
+ await service(api2, args.serviceKey, secrets);
34131
+ return { data: { ...await api2.exportRequest(args.serviceKey, args.requestId), notice } };
34132
+ });
34133
+ tool("save_check", "Explicitly save a recorded GET/HEAD as a status check without running it. Credentials and arbitrary destinations cannot be supplied.", { projectId: id, ...serviceInput, requestId: id, name: exports_external.string().min(1).max(200), expectedStatus: exports_external.number().int().min(100).max(599) }, true, async (args, api2, secrets) => {
34134
+ serviceScope(args.projectId, [args.serviceKey]);
34135
+ await project(api2, args.projectId, secrets);
34136
+ await service(api2, args.serviceKey, secrets);
34137
+ return { data: { ...await api2.saveCheck(args.projectId, { serviceKey: args.serviceKey, requestId: args.requestId, name: args.name, expectedStatus: args.expectedStatus }), notice } };
34138
+ });
34139
+ tool("list_checks", "Read saved project status checks and their retained results without running requests.", { projectId: id }, false, async (args, api2, secrets) => {
34140
+ await project(api2, args.projectId, secrets);
34141
+ return { data: { ...await api2.checks(args.projectId), notice } };
34142
+ });
34143
+ tool("run_check", "Explicitly run one saved GET/HEAD check against its current local service. Even GET can have side effects. No automatic retries.", { projectId: id, checkId: id }, true, async (args, api2, secrets) => {
34144
+ await project(api2, args.projectId, secrets);
34145
+ return { data: { ...await api2.runCheck(args.projectId, args.checkId), notice } };
34146
+ });
34085
34147
  for (const action of ["start", "restart"])
34086
34148
  tool(`${action}_service`, `${action === "start" ? "Start a configured service and its prerequisites in the background" : "Restart a daemon-owned configured service; terminal-owned services must be restarted in their terminal"}. May execute project commands.`, serviceInput, true, async (args, api2, secrets) => {
34087
34149
  const target = await service(api2, args.serviceKey, secrets, true);
@@ -34104,7 +34166,7 @@ function createMcpServer(options) {
34104
34166
  if (await api2.shareInfo(args.serviceKey))
34105
34167
  throw new Error("Service is already shared; use get_share or stop_share first");
34106
34168
  const password = args.public ? undefined : crypto.randomBytes(12).toString("base64url");
34107
- return { data: { share: await api2.share(args.serviceKey, password, true) }, password };
34169
+ return { data: { share: await api2.share(args.serviceKey, password, true, args.public) }, password };
34108
34170
  });
34109
34171
  tool("stop_share", "Close the public tunnel while leaving the local service running.", serviceInput, true, async (args, api2, secrets) => {
34110
34172
  await service(api2, args.serviceKey, secrets);
@@ -3,7 +3,7 @@ import {
3
3
  STATE_FILE,
4
4
  assertUserRuntime,
5
5
  findDaemon
6
- } from "./index-wtqj3xaj.js";
6
+ } from "./index-tv699dzb.js";
7
7
 
8
8
  // src/update.ts
9
9
  import { spawn } from "node:child_process";
package/docs/dashboard.md CHANGED
@@ -46,7 +46,7 @@ Tunnel requests and sharing events have a **SHARE** label and distinct color. Re
46
46
 
47
47
  ## Public sharing
48
48
 
49
- Install [cloudflared](https://developers.cloudflare.com/tunnel/downloads/) separately. Start an HTTP service, choose **Share**, enter an optional password, and submit. Leaving the password blank makes the link public. Database services cannot be shared over HTTP.
49
+ Install [cloudflared](https://developers.cloudflare.com/tunnel/downloads/) separately. Start an HTTP service, choose **Share**, enter an optional password, and submit. Leaving the password blank generates one; reveal it with **Show** next to the link. To share without a password, tick **Public link** explicitly. Database services cannot be shared over HTTP.
50
50
 
51
51
  The menu closes on submission. The generated URL appears below the local URL with a spinner until verified. At one minute, a hint suggests opening it manually; a request reaching the public tunnel can confirm readiness even when the daemon's DNS probe fails. Startup has a two-minute limit. Failure closes the tunnel and shows an inline error with retry guidance.
52
52
 
@@ -85,3 +85,23 @@ Traffic must pass through a LocalDeck service URL to be captured. Calls directly
85
85
  For an occupied startup port, service errors show recovery guidance with the raw output under Technical details. Vite guidance covers `strictPort` and reading `PORT`; LocalDeck does not change your application configuration or terminate unrelated listeners.
86
86
 
87
87
  Clean URLs and public links are distinct browser origins. Your API must allow the frontend origin you visit. Injected `LOCALDECK_URL` and service URL references use stable numeric localhost addresses for service-to-service communication. See [configuration](configuration.md).
88
+
89
+ ## Activity and Since my change
90
+
91
+ Open a project and select **Activity** to combine request records, logs, and service lifecycle transitions. The default **Incidents** view emphasizes failures and state changes; **All activity**, service filters, and search narrow the stream. Inspecting an event pauses live updates so the evidence stays in place. Event links can be copied and reopened; expired or cleared records are identified instead of silently replaced. Nearby events are temporal context, not proof that a request caused a log line.
92
+
93
+ Use **Mark baseline** before changing code. LocalDeck saves a summary of the preceding five minutes and compares it with later observations for the same services. Comparisons show sample counts, error rates, restarts, error signatures, and route latency. Windows may have different lengths. “Not observed again” does not mean verified fixed; sparse traffic and incomplete history are explicitly reported. The latest 50 baselines are retained across restarts. Logs and requests keep their existing bounded history, and the lifecycle journal retains the latest 2,000 transitions. Clearing logs or requests also removes those records from Activity.
94
+
95
+ ## Worktree environments
96
+
97
+ The project environment selector discovers existing Git worktrees from the selected repository, including matching nested project configurations. Choose **Register & switch** to add a sibling worktree, or **Switch** for one already registered. Discovery does not start services. Branch names, detached HEADs, directories, availability, and running counts identify the selected environment.
98
+
99
+ Each worktree retains its path-based project ID and independent automatic ports. Copied nicknames are qualified when needed to avoid routing to another environment. Conflicting explicitly pinned ports are reported; LocalDeck does not override them. Databases, Docker resources, and external services can still be shared through configuration. Secrets are not copied. This release discovers existing worktrees; it does not create or delete them.
100
+
101
+ ## Saved reproduction checks
102
+
103
+ In a request inspector, choose **Preview sanitized request**, review omitted values, then copy cURL or structured JSON. Exported headers are restricted to representation metadata, recognized credentials are removed, and unsupported/incomplete bodies are omitted. Review the preview before sharing it: application data may still be private.
104
+
105
+ Eligible GET/HEAD requests can be saved with a name and an explicitly chosen expected HTTP status. Open the project's **Checks** tab to run them. Saving never executes a request. Runs use the selected environment's current local service, omit credentials, do not follow redirects, and retain the existing five-second/64-KiB replay bounds. Secret-bearing paths and requests with bodies cannot be saved as executable checks. Even GET can have application side effects.
106
+
107
+ Check definitions survive expiration or clearing of the source request. The latest 200 checks and 20 results per check are retained. Results distinguish an expected status, a status mismatch, and transport failure; response bodies are not persisted in run history.
package/docs/mcp.md CHANGED
@@ -56,6 +56,16 @@ A protocol connection alone starts no apps. The first tool call starts the daemo
56
56
  | `get_logs` | Read recent logs, optionally filtered by `stream`: stdout, stderr or system. |
57
57
  | `get_requests` | Read HTTP request records, captured headers, and body previews when available, optionally filtered by exact `status`, `method`, or a `path` substring. |
58
58
  | `get_response` | Read a cached response body using the exact service key and request ID from `get_requests`; does not replay the request. |
59
+ | `get_timeline` | Read project activity with optional exact `services`, `filter` (`all` or `incidents`), `query`, `cursor`, and `limit` (maximum 200). Returns `items`, optional `nextCursor`, and `coverage`. |
60
+ | `mark_baseline` | Explicitly save a marker for `projectId`, optional exact `services` and `label`. Does not run requests. |
61
+ | `list_baselines` | Read saved markers for a project. |
62
+ | `compare_baseline` | Compare a project's `baselineId`: before/after evidence, `newlyObserved`, `notObservedAgain`, `insufficientEvidence`, and `coverage`. |
63
+ | `list_environments` | Discover a project's Git worktree environments without registering or starting them. With `--project`, returns only that project's environment. |
64
+ | `register_environment` | Explicitly register a discovered `environmentId`. Disabled with `--project`, because registration can add another project. |
65
+ | `export_request` | Export a redacted reproduction for an exact service key and request ID without executing it. |
66
+ | `save_check` | Save a recorded GET/HEAD using `projectId`, `serviceKey`, `requestId`, `name`, and `expectedStatus` (100–599), without running it. |
67
+ | `list_checks` | Read a project's saved checks and retained results. |
68
+ | `run_check` | Explicitly execute one saved `checkId` against its current local service. Even GET can have side effects. |
59
69
  | `start_service` | Start a configured service and prerequisites in the background, using the daemon supervisor. |
60
70
  | `stop_service` | Stop a registered service and its share. Terminal services receive the existing stop request. |
61
71
  | `restart_service` | Restart a configured daemon-owned service. Active terminal services must be restarted in their original terminal. |
@@ -64,7 +74,7 @@ A protocol connection alone starts no apps. The first tool call starts the daemo
64
74
  | `start_share` | Create a Cloudflare quick share, generating a password and returning it once. `public: true` explicitly removes password protection. Requires cloudflared. |
65
75
  | `stop_share` | Close the tunnel while keeping the local service running. |
66
76
 
67
- List tools accept `limit` (default 50, maximum 200). Logs and request filters search the last 200 retained records; they are not full-history queries. Responses contain structured data and a JSON text fallback, capped together at 64 KiB, with `truncated` when information is omitted. No infinite log-follow operation is exposed.
77
+ `list_projects`, `list_services`, `get_logs`, `get_requests`, and `get_timeline` accept `limit` (default 50, maximum 200). Logs and request filters search the last 200 retained records; they are not full-history queries. Responses contain structured data and a JSON text fallback, capped together at 64 KiB, with `truncated` when information is omitted. No infinite log-follow operation is exposed.
68
78
 
69
79
  ## Actions and data
70
80
 
@@ -129,7 +139,7 @@ Bodies live in the daemon's shared 64 MiB cache. They can be evicted or lost on
129
139
 
130
140
  1. If an action times out, do not repeat it immediately: it may already have completed.
131
141
  2. Call `get_service` or `get_share` to inspect the resulting state. Each tool call rediscovers the daemon connection.
132
- 3. Retry only after establishing that the requested change is still needed. Log and request reads are bounded snapshots; there is no live-follow tool or pagination cursor.
142
+ 3. Retry only after establishing that the requested change is still needed. Log and request reads are bounded snapshots; there is no live-follow tool. Timeline reads provide an opaque pagination cursor for retained activity.
133
143
 
134
144
  ### Share a service for review
135
145
 
@@ -138,3 +148,15 @@ Bodies live in the daemon's shared 64 MiB cache. They can be evicted or lost on
138
148
  3. When sharing is authorized, call `start_share`; password protection is the default. Use `public: true` only when unprotected public access is intended.
139
149
  4. Return the URL and the newly generated password from that result. `get_share` cannot recover the password later.
140
150
  5. Call `stop_share` when the share is no longer needed; the local service stays running.
151
+
152
+
153
+ ### Compare evidence around a fix
154
+
155
+ 1. Discover the project and exact service keys. Read `get_timeline` with `filter: "incidents"`; use `nextCursor` for another retained page and inspect `coverage`.
156
+ 2. Before changing code, explicitly call `mark_baseline` with the project, optional service keys, and a useful label.
157
+ 3. Make the change and generate representative traffic only when authorized. Marking or comparing a baseline never sends requests automatically.
158
+ 4. Call `compare_baseline` using the marker's ID. Report newly observed evidence and evidence not observed again separately. Missing retention or traffic can make a comparison inconclusive; absence of an incident does not prove a fix.
159
+ 5. Use `export_request` to inspect a redacted reproduction without executing it. Missing credentials and omitted bodies can limit reproducibility.
160
+ 6. For an authorized repeatable GET/HEAD check, call `save_check` with the expected status, then explicitly `run_check`. Saving and listing checks never runs them. Check execution retains the same local-destination and credential restrictions as replay.
161
+
162
+ Environment discovery is read-only. `register_environment` remembers an explicitly selected worktree and does not launch it. Project-restricted MCP servers reject registration; use an unrestricted server or register through the dashboard when that broader action is intended.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "localdeck",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "type": "module",
5
5
  "description": "Stable ports, shareable HTTPS links and request logs for your local dev servers",
6
6
  "bin": {
@@ -1 +0,0 @@
1
- :root{color-scheme:dark;--bg: #191b1d;--bg-2: #202325;--bg-3: #24282b;--border: #353a3d;--text: #ededed;--muted: #a0a6aa;--accent: #b6ef6b;--accent-hover: #c5f58a;--accent-ink: #1c2515;--selected: #293322;--input-border: #515b61;--radius: 6px;--orange: #b6ef6b;--ok: #b6ef6b;--warn: #d1b576;--err: #ed9e91;--share: #91c7c4;--chrome: #191b1d;--chrome-border: #353a3d;--chrome-text: #ededed;--chrome-muted: #a0a6aa;--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;--sans: var(--mono);--text-small: .75rem;--text-body: .8125rem;--text-label: .875rem;--text-section: 1rem;--text-title: 2.25rem;--control-height: 2.25rem}*{box-sizing:border-box}html{font-size:100%}html,body{margin:0;min-width:320px;height:100%;background:var(--chrome);color:var(--text);font-family:var(--sans);line-height:1.5}body{font-size:var(--text-body)}#app{height:100%}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}button{display:inline-flex;align-items:center;justify-content:center;text-align:center;gap:6px;font:500 var(--text-body)/1.4 var(--sans);color:var(--text);background:transparent;border:1px solid var(--border);border-radius:var(--radius);padding:7px 12px;min-height:var(--control-height);cursor:pointer;white-space:nowrap}button:hover{background:var(--bg-2);border-color:var(--muted)}button.primary{background:var(--accent);border-color:var(--accent);color:var(--accent-ink)}button.primary:hover{background:var(--accent-hover);border-color:var(--accent-hover)}button.danger:hover{color:var(--err);border-color:var(--err)}button:disabled{opacity:.45;cursor:default}input,select,textarea{font:inherit;color:var(--text);background:transparent;border:1px solid var(--border);border-radius:var(--radius);padding:8px 10px}input:not([type=checkbox]):not([type=radio]),select{min-height:var(--control-height)}input,select,textarea{font-size:var(--text-body);border-color:var(--input-border)}input[type=checkbox]{accent-color:var(--accent)}button:focus-visible,a:focus-visible,summary:focus-visible{outline:2px solid var(--accent);outline-offset:3px}code,.mono{font-family:var(--mono);font-size:var(--text-small)}::-webkit-scrollbar{width:7px;height:7px}::-webkit-scrollbar-thumb{background:#8f8f8560}[hidden]{display:none!important}summary{cursor:pointer}pre{overflow:auto}@media(prefers-reduced-motion:reduce){*,*:before,*:after{scroll-behavior:auto!important;transition:none!important}}@media(pointer:coarse),(max-width:760px){:root{--control-height: 2.75rem}input:not([type=checkbox]):not([type=radio]),select,textarea{font-size:1rem}}label:has(input[type=checkbox]){min-height:var(--control-height)}input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--accent);outline-offset:3px}::selection{background:var(--accent);color:var(--accent-ink)}input::placeholder,textarea::placeholder{color:#929a9f}button,a{-webkit-tap-highlight-color:transparent;transition:background .12s,border-color .12s,color .12s}button:active:not(:disabled){transform:translateY(1px)}h1{font-weight:400;letter-spacing:-.04em}@media(prefers-reduced-motion:reduce){button,a{transition:none}button:active:not(:disabled){transform:none}}:root{--page-padding-x: 40px;--page-padding-y: 48px}.page-shell{width:100%;min-width:0;padding:var(--page-padding-y) var(--page-padding-x)}.page-heading{display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:20px;margin:0 0 24px}.page-heading h1{margin:0;font:400 var(--text-title)/1.25 var(--sans);letter-spacing:-.04em;overflow-wrap:anywhere}.page-heading .page-subtitle{margin:8px 0 0;color:var(--muted);font:400 var(--text-small)/1.5 var(--sans)}@media(max-width:760px){:root{--page-padding-x: 16px;--page-padding-y: 24px;--text-title: 1.5rem}.page-heading{gap:12px}}dialog.svelte-vw7b2v{width:min(640px,calc(100vw - 32px));max-height:min(620px,80dvh);margin:12dvh auto auto;padding:0;border:1px solid var(--border);background:var(--bg);color:var(--text);box-shadow:0 24px 80px #0005;overflow:hidden;border-radius:8px}dialog[open].svelte-vw7b2v{display:flex;flex-direction:column}dialog.svelte-vw7b2v::backdrop{background:#1119}.search-row.svelte-vw7b2v{display:flex;align-items:center;gap:12px;padding:14px 18px;border-bottom:1px solid var(--border);flex-shrink:0}.search-row.svelte-vw7b2v svg{flex-shrink:0;color:var(--muted)}input.svelte-vw7b2v{flex:1;min-width:0;border:0;padding:8px 0}.close.svelte-vw7b2v{min-height:28px;padding:3px 6px;font:12px var(--mono);color:var(--muted)}.results.svelte-vw7b2v{overflow:auto;min-height:0;padding:6px}.results.svelte-vw7b2v button:where(.svelte-vw7b2v){width:100%;justify-content:space-between;gap:16px;border:0;padding:12px;text-align:left;white-space:normal}.results.svelte-vw7b2v button.chosen:where(.svelte-vw7b2v){background:var(--accent);color:var(--accent-ink)}.results.svelte-vw7b2v button.chosen:where(.svelte-vw7b2v) small:where(.svelte-vw7b2v){color:var(--accent-ink)}.search-row.svelte-vw7b2v:focus-within{border-bottom-color:var(--accent)}.search-row.svelte-vw7b2v input:where(.svelte-vw7b2v):focus-visible{outline:none}.results.svelte-vw7b2v button[aria-disabled=true]:where(.svelte-vw7b2v){color:var(--muted)}strong.svelte-vw7b2v,small.svelte-vw7b2v{display:block}strong.svelte-vw7b2v{font-weight:500}small.svelte-vw7b2v{font-size:var(--text-small);color:var(--muted);overflow-wrap:anywhere}footer.svelte-vw7b2v{display:flex;justify-content:space-between;gap:12px;padding:12px 18px;border-top:1px solid var(--border);font-size:var(--text-small);color:var(--muted);flex-shrink:0}.separator.svelte-vw7b2v{margin:0 6px}.empty.svelte-vw7b2v,.error.svelte-vw7b2v{padding:16px 18px;margin:0}.empty.svelte-vw7b2v{color:var(--muted)}.error.svelte-vw7b2v{color:var(--err)}.load-error.svelte-vw7b2v{display:flex;align-items:center;gap:12px;padding:12px 18px;color:var(--err);border-top:1px solid var(--border);font-size:var(--text-small)}@media(max-width:480px){dialog.svelte-vw7b2v{margin-top:6dvh;max-height:88dvh}.search-row.svelte-vw7b2v{gap:8px;padding:10px}footer.svelte-vw7b2v{flex-wrap:wrap}}section.svelte-1hauwfi{margin-top:32px}h3.svelte-1hauwfi{margin:0 0 12px;font:500 var(--text-section)/1.4 var(--sans)}p.svelte-1hauwfi,li.svelte-1hauwfi{line-height:1.7}p.svelte-1hauwfi{color:var(--muted)}ul.svelte-1hauwfi{padding-left:20px}code.svelte-1hauwfi{overflow-wrap:anywhere}.copy-command.svelte-1hauwfi{display:inline-flex;vertical-align:middle;padding:4px;margin-left:5px;min-height:28px;width:28px;border:0;border-radius:0;background:transparent;color:var(--muted)}.copy-command.svelte-1hauwfi:hover{background:transparent;color:var(--text)}.sr-only.svelte-1hauwfi{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}li.svelte-1hauwfi+li:where(.svelte-1hauwfi){margin-top:8px}h2.svelte-1vwaeio{font-size:var(--text-section);line-height:1.4;margin:0 0 12px;font-weight:600}p.svelte-1vwaeio{line-height:1.6;margin:0 0 16px;max-width:65ch}.about.svelte-1vwaeio>section:where(.svelte-1vwaeio){margin-top:40px;padding-top:28px;border-top:1px solid var(--border)}.steps.svelte-1vwaeio{padding-left:24px;margin:20px 0}.steps.svelte-1vwaeio li:where(.svelte-1vwaeio){padding-left:8px;margin-bottom:16px}.steps.svelte-1vwaeio span:where(.svelte-1vwaeio){display:block;margin-top:4px;color:var(--muted)}.note.svelte-1vwaeio{color:var(--muted);font-size:var(--text-small)}code.svelte-1vwaeio{overflow-wrap:anywhere}.command-groups.svelte-1vwaeio{margin:24px 0;border-top:1px solid var(--border)}details.svelte-1vwaeio{border-bottom:1px solid var(--border)}summary.svelte-1vwaeio{padding:16px 0;min-height:var(--control-height);font-weight:500}summary.svelte-1vwaeio span:where(.svelte-1vwaeio){color:var(--muted);font-size:var(--text-small);font-weight:400;margin-left:12px;white-space:nowrap}dl.svelte-1vwaeio{margin:0 0 16px}dl.svelte-1vwaeio>div:where(.svelte-1vwaeio){padding:16px 0}dl.svelte-1vwaeio>div:where(.svelte-1vwaeio)+div:where(.svelte-1vwaeio){border-top:1px solid var(--border)}dt.svelte-1vwaeio{margin-bottom:8px}dd.svelte-1vwaeio{margin:0;color:var(--muted);line-height:1.6;max-width:65ch}@media(max-width:760px){summary.svelte-1vwaeio span:where(.svelte-1vwaeio){display:block;margin:4px 0 0 18px}}h3.svelte-1offp6a{margin-top:32px;font:500 var(--text-section)/1.4 var(--sans)}label.svelte-1offp6a{display:block;margin-bottom:8px}.controls.svelte-1offp6a{display:flex;align-items:center;gap:8px}input.svelte-1offp6a{width:160px}input.svelte-1offp6a:focus-visible{outline:none;border-color:var(--text)}p.svelte-1offp6a{color:var(--muted);line-height:1.6}.current.svelte-1offp6a{font-size:var(--text-small)}article.svelte-1tl5wtp{padding:28px 0;border-bottom:1px solid var(--border)}h2.svelte-1tl5wtp{margin:0 0 16px;font:500 var(--text-section)/1.4 var(--sans)}h2.svelte-1tl5wtp span:where(.svelte-1tl5wtp){color:var(--muted);margin-left:12px;font-size:var(--text-label)}h3.svelte-1tl5wtp{font-size:var(--text-label);font-weight:500;margin:0 0 10px}.release-section.svelte-1tl5wtp{max-width:100ch;margin-top:28px}li.svelte-1tl5wtp+li:where(.svelte-1tl5wtp){margin-top:8px}ul.svelte-1tl5wtp{margin:0;padding-left:20px;line-height:1.8}.secrets.svelte-sh54s5{margin-top:28px}h2.svelte-sh54s5{font-size:18px;margin:0;font-weight:500}.section-heading.svelte-sh54s5{display:flex;align-items:center;justify-content:space-between;gap:16px}.count.svelte-sh54s5,.section-note.svelte-sh54s5{font-size:var(--text-small);color:var(--muted)}.section-note.svelte-sh54s5{margin:8px 0 22px;line-height:1.6}ul.svelte-sh54s5{padding:0;margin:0;list-style:none}.list-heading.svelte-sh54s5,li.svelte-sh54s5{display:grid;grid-template-columns:minmax(0,1fr) 130px minmax(124px,auto);gap:16px;align-items:center}.list-heading.svelte-sh54s5{padding:0 0 10px;border-bottom:1px solid var(--border);color:var(--muted);font-size:var(--text-small)}li.svelte-sh54s5{min-height:66px;padding:12px 0;border-bottom:1px solid var(--border)}code.svelte-sh54s5{overflow-wrap:anywhere;font-size:var(--text-small)}.secret-status.svelte-sh54s5{display:flex;gap:7px;align-items:center;font-size:var(--text-small);color:var(--muted)}.secret-status.svelte-sh54s5 i:where(.svelte-sh54s5){width:6px;height:6px;flex-shrink:0;background:currentColor}.secret-status.configured.svelte-sh54s5{color:var(--ok)}.actions.svelte-sh54s5{display:flex;justify-content:flex-end;gap:14px}.text-action.svelte-sh54s5{padding:4px 0;min-height:32px;border:0;background:transparent;font-size:var(--text-small);text-decoration:underline;text-underline-offset:4px}.text-action.svelte-sh54s5:hover{text-decoration-thickness:2px}.remove.svelte-sh54s5{color:var(--muted)}.count.svelte-sh54s5{display:block;margin-top:6px}.secret-editor.svelte-sh54s5{position:relative}summary.svelte-sh54s5{cursor:pointer;list-style:none;display:inline-flex;gap:7px;align-items:center;justify-content:center}summary.svelte-sh54s5::-webkit-details-marker{display:none}summary[aria-disabled=true].svelte-sh54s5{opacity:.5;cursor:default}.add-secret.svelte-sh54s5{padding:7px 12px;min-height:var(--control-height);border:1px solid var(--border);font-weight:500}.add-secret.svelte-sh54s5:hover{background:var(--bg-2)}.editor-popover.svelte-sh54s5{position:absolute;top:calc(100% + 8px);right:0;z-index:40;width:min(340px,calc(100vw - 110px));padding:20px;background:var(--bg);border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 24px #0005;text-align:left}.editor-popover.svelte-sh54s5 h2:where(.svelte-sh54s5){margin-bottom:20px}.editor-popover.svelte-sh54s5 .section-note:where(.svelte-sh54s5){margin:14px 0}.editor-popover.svelte-sh54s5 .primary:where(.svelte-sh54s5){width:100%}.fields.svelte-sh54s5{display:grid;grid-template-columns:1fr;gap:16px}label.svelte-sh54s5{display:grid;gap:8px;font-size:var(--text-small);font-weight:500;min-width:0}input.svelte-sh54s5{width:100%;min-width:0;background:var(--bg)}.storage-note.svelte-sh54s5{margin-top:28px;padding-left:14px;border-left:2px solid var(--border);max-width:680px;font-size:var(--text-small);color:var(--muted)}.storage-note.svelte-sh54s5 strong:where(.svelte-sh54s5){font-weight:500;color:var(--text)}.storage-note.svelte-sh54s5 p:where(.svelte-sh54s5){margin:6px 0 0;line-height:1.6}.error.svelte-sh54s5{color:var(--err)}.saved.svelte-sh54s5{color:var(--ok)}.empty.svelte-sh54s5{padding:22px 0;border-block:1px solid var(--border);color:var(--muted)}@media(max-width:1000px){.list-heading.svelte-sh54s5{display:none}li.svelte-sh54s5{grid-template-columns:minmax(0,1fr) auto;gap:8px 16px}.secret-status.svelte-sh54s5{grid-column:1;grid-row:2}.actions.svelte-sh54s5{grid-column:2;grid-row:1 / 3}}@media(max-width:650px){.fields.svelte-sh54s5{grid-template-columns:1fr;gap:16px}form.svelte-sh54s5{padding:18px}}.share-line.svelte-iodvty{display:flex;align-items:center;flex-wrap:wrap;gap:8px 16px;min-width:0}.slow-hint.svelte-iodvty{flex-basis:100%;color:var(--muted);font-size:var(--text-small)}.slow-hint.svelte-iodvty a:where(.svelte-iodvty){text-decoration:underline}.password.svelte-iodvty{display:inline-flex;flex-wrap:wrap;align-items:center;gap:7px;color:var(--muted);font-size:var(--text-small);overflow-wrap:anywhere}.password.svelte-iodvty button:where(.svelte-iodvty){padding:0;min-height:0;border:0;background:transparent;text-decoration:underline;color:inherit}a.svelte-iodvty{width:fit-content;max-width:100%;overflow-wrap:anywhere;color:var(--share);font-size:var(--text-small)}.connecting.svelte-iodvty,.error.svelte-iodvty{display:flex;align-items:center;gap:7px;font-size:var(--text-small)}.connecting.svelte-iodvty{flex-wrap:wrap;color:var(--muted)}.error.svelte-iodvty{color:var(--err);align-items:flex-start;overflow-wrap:anywhere}.error.svelte-iodvty svg{flex-shrink:0;margin-top:3px}.spinner.svelte-iodvty{display:inline-flex;animation:svelte-iodvty-spin 1s linear infinite}@keyframes svelte-iodvty-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.spinner.svelte-iodvty{animation:none}}.menu-item.svelte-1u7hpyn>summary:where(.svelte-1u7hpyn){justify-content:flex-start;gap:10px;border:0;font-weight:400;padding:10px 12px}.menu-item.svelte-1u7hpyn .share-popover:where(.svelte-1u7hpyn){position:static;width:100%;box-shadow:none;border:0;padding:12px}.share-control.svelte-1u7hpyn{position:relative}summary.svelte-1u7hpyn{display:flex;align-items:center;justify-content:center;gap:7px;padding:7px 12px;min-height:var(--control-height);border:1px solid var(--border);list-style:none;font-weight:500}summary.svelte-1u7hpyn::-webkit-details-marker{display:none}summary.svelte-1u7hpyn:hover{background:var(--bg-2);border-color:var(--muted)}.share-popover.svelte-1u7hpyn{position:absolute;right:0;top:calc(100% + 8px);z-index:30;width:min(340px,calc(100vw - 96px));padding:18px;border:1px solid var(--border);background:var(--bg);border-radius:8px;box-shadow:0 8px 24px #0005;text-align:left}.share-url.svelte-1u7hpyn{display:block;margin-top:12px;overflow-wrap:anywhere}p.svelte-1u7hpyn{font-size:var(--text-small);color:var(--muted);line-height:1.6;overflow-wrap:anywhere}label.svelte-1u7hpyn{display:grid;gap:6px;font-size:var(--text-small)}input.svelte-1u7hpyn{width:100%;min-width:0}.actions.svelte-1u7hpyn{display:flex;flex-wrap:wrap;gap:8px}.error.svelte-1u7hpyn{color:var(--err)}.service-options.svelte-m918bk{position:relative}summary.svelte-m918bk{display:flex;align-items:center;justify-content:center;gap:8px;padding:7px 12px;min-height:var(--control-height);border:1px solid var(--border);border-radius:var(--radius);list-style:none;font-weight:500;cursor:pointer}summary.svelte-m918bk::-webkit-details-marker{display:none}summary.svelte-m918bk:hover,.service-options[open].svelte-m918bk>summary:where(.svelte-m918bk){background:var(--bg-2);border-color:var(--muted)}.options-popover.svelte-m918bk{position:absolute;z-index:40;right:0;top:calc(100% + 6px);width:min(300px,calc(100vw - 100px));padding:6px;background:var(--bg);border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 24px #0005}.options-popover.svelte-m918bk>button:where(.svelte-m918bk){width:100%;border:0;background:transparent;display:flex;gap:10px;justify-content:flex-start;padding:10px 12px;text-align:left;font-weight:400}.options-popover.svelte-m918bk>button:where(.svelte-m918bk):hover{background:var(--bg-2)}.share-action.svelte-m918bk{border-top:1px solid var(--border);margin-top:4px;padding-top:4px}.service-error.svelte-13pnhk8{padding:12px 0;font-size:var(--text-small);overflow-wrap:anywhere}strong.svelte-13pnhk8,p.svelte-13pnhk8{color:var(--err)}p.svelte-13pnhk8{margin:0;white-space:pre-wrap}ul.svelte-13pnhk8{margin:8px 0 12px;padding-left:20px;color:var(--text);line-height:1.7}li.svelte-13pnhk8+li:where(.svelte-13pnhk8){margin-top:6px}summary.svelte-13pnhk8{width:fit-content;color:var(--muted)}pre.svelte-13pnhk8{margin:8px 0 0;white-space:pre-wrap;font:inherit;font-family:var(--mono);color:var(--muted)}.diagnostics.svelte-1xk5qw3{margin:16px 0;padding:12px;border:1px solid var(--border);border-radius:0}summary.svelte-1xk5qw3{cursor:pointer}p.svelte-1xk5qw3{color:var(--muted);font-size:var(--text-small)}textarea.svelte-1xk5qw3{display:block;width:100%;box-sizing:border-box;margin:8px 0;background:var(--bg-2);color:var(--text);border:1px solid var(--border);font:var(--text-small)/1.5 var(--mono)}form.svelte-1gn57ev{width:100%;min-width:0;padding:20px 0 8px}fieldset.svelte-1gn57ev{border:0;padding:0;margin:0;min-width:0}.fields.svelte-1gn57ev{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}label.svelte-1gn57ev{display:flex;flex-direction:column;gap:6px;font-size:var(--text-body);color:var(--text)}input.svelte-1gn57ev,select.svelte-1gn57ev{width:100%;min-width:0;background:var(--bg)}.dependencies.svelte-1gn57ev{margin:20px 0;font-size:var(--text-small)}.dependencies.svelte-1gn57ev>div:where(.svelte-1gn57ev){display:flex;flex-wrap:wrap;gap:16px;margin-top:8px}.dependencies.svelte-1gn57ev label:where(.svelte-1gn57ev){flex-direction:row;align-items:center}.dependencies.svelte-1gn57ev input:where(.svelte-1gn57ev){width:auto}p.svelte-1gn57ev{color:var(--muted);font-size:var(--text-small)}.error.svelte-1gn57ev{color:var(--err)}@media(max-width:600px){.fields.svelte-1gn57ev{grid-template-columns:1fr}}form.svelte-ruqv4s{margin:24px 0 40px;max-width:480px}label.svelte-ruqv4s{font-weight:600}p.svelte-ruqv4s{font-size:var(--text-small);color:var(--muted);line-height:1.6}.controls.svelte-ruqv4s{display:flex;gap:8px;margin-top:8px}input.svelte-ruqv4s{flex:1;width:180px;min-width:0}.error.svelte-ruqv4s{color:var(--err);margin-bottom:0}.notice.svelte-ruqv4s{color:var(--ok);margin-bottom:0}.hint.svelte-ruqv4s{font-size:var(--text-small)}.service-port.svelte-1poq4pf{display:flex;flex-wrap:wrap;gap:4px 16px;color:var(--muted);font-size:var(--text-small);line-height:1.5}strong.svelte-1poq4pf{color:var(--text);font-weight:500}.project-overview.svelte-l7un4l{min-height:100%;display:flex;flex-direction:column}.heading.svelte-l7un4l{min-width:0}h1.svelte-l7un4l,h2.svelte-l7un4l,p.svelte-l7un4l{margin-top:0}h2.svelte-l7un4l{margin-bottom:8px;font-size:var(--text-section)}.project-actions.svelte-l7un4l,.service-actions.svelte-l7un4l,.recovery-actions.svelte-l7un4l{display:flex;align-items:center;gap:8px}.summary-row.svelte-l7un4l{display:flex;flex-wrap:wrap;gap:14px;padding:4px 0;color:var(--muted);font-size:var(--text-small)}.summary-row.svelte-l7un4l strong:where(.svelte-l7un4l){color:var(--text);font-variant-numeric:tabular-nums}.bulk-summary.svelte-l7un4l{margin:0 0 10px;color:var(--muted)}.service-grid.svelte-l7un4l{display:grid;grid-template-columns:1fr;gap:0}.service-card.svelte-l7un4l{min-width:0;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;column-gap:16px;padding:22px 0;border-bottom:1px solid var(--border)}.service-card.failed.svelte-l7un4l{border-color:#f87171a6}.service-main.svelte-l7un4l{display:grid;grid-template-columns:minmax(0,1fr);justify-content:stretch;width:100%;padding:0;border:0;background:transparent;text-align:left;white-space:normal}.service-main.svelte-l7un4l:hover{background:transparent}.service-heading.svelte-l7un4l{display:flex;align-items:center;gap:8px}.status-dot.svelte-l7un4l{width:7px;height:7px;border-radius:50%;background:var(--muted)}.status-dot.running.svelte-l7un4l{background:var(--ok)}.status-dot.waiting.svelte-l7un4l{background:var(--warn)}.status-text.svelte-l7un4l{margin-left:8px;color:var(--muted);font-size:var(--text-small);background:var(--bg-3);border-radius:var(--radius);padding:2px 7px}.ownership.svelte-l7un4l{margin:4px 0 8px 15px;color:var(--muted);font-size:var(--text-small)}.service-url.svelte-l7un4l{color:var(--accent);overflow:hidden;text-overflow:ellipsis}.command.svelte-l7un4l{margin-top:7px;color:var(--text);overflow:hidden;text-overflow:ellipsis}.dependency-summary.svelte-l7un4l{margin-top:7px;color:var(--accent);font-size:var(--text-small)}.dependency-summary.svelte-l7un4l span:where(.svelte-l7un4l){color:var(--muted)}.dependency-editor.svelte-l7un4l{margin-top:10px;padding-top:9px;border-top:1px solid var(--border)}.dependency-editor.svelte-l7un4l summary:where(.svelte-l7un4l){color:var(--muted);cursor:pointer;font-size:var(--text-small)}.dependency-editor.svelte-l7un4l form:where(.svelte-l7un4l){margin-top:9px}.dependency-editor.svelte-l7un4l fieldset:where(.svelte-l7un4l){display:flex;flex-wrap:wrap;gap:7px 12px;margin:0;padding:0;border:0}.dependency-editor.svelte-l7un4l legend:where(.svelte-l7un4l){width:100%;margin-bottom:6px;color:var(--muted);font-size:var(--text-small)}.dependency-editor.svelte-l7un4l label:where(.svelte-l7un4l){display:inline-flex;align-items:center;gap:5px;font-size:var(--text-small)}.dependency-actions.svelte-l7un4l{display:flex;gap:7px;margin-top:10px}.no-dependencies.svelte-l7un4l{color:var(--muted);font-size:var(--text-small)}.inline-error.svelte-l7un4l{margin:9px 0 0;color:var(--err);font-size:var(--text-small);overflow-wrap:anywhere}.unavailable.svelte-l7un4l{display:flex;justify-content:space-between;align-items:center;gap:20px;margin-top:16px;padding:16px;border:1px solid rgba(248,113,113,.55);border-radius:var(--radius);background:var(--bg-2)}.unavailable.svelte-l7un4l p:where(.svelte-l7un4l),.empty-services.svelte-l7un4l p:where(.svelte-l7un4l){margin-bottom:0;color:var(--muted)}.active-services.svelte-l7un4l{margin-top:18px}.active-services.svelte-l7un4l>h2:where(.svelte-l7un4l){margin-bottom:3px}.active-services.svelte-l7un4l>p:where(.svelte-l7un4l){margin-bottom:10px;color:var(--muted)}.empty-services.svelte-l7un4l{grid-column:1 / -1;padding:24px;border:1px dashed var(--border);border-radius:var(--radius);text-align:center}.launch-panel.svelte-l7un4l{padding:20px;background:var(--bg-2);border:1px solid var(--err);border-radius:var(--radius);margin-bottom:32px}.launch-panel.svelte-l7un4l h2:where(.svelte-l7un4l){font-size:var(--text-section);letter-spacing:-.025em;margin:7px 0}.launch-panel.svelte-l7un4l p:where(.svelte-l7un4l):last-child{color:var(--muted);margin-bottom:0;font-size:var(--text-small)}.open-app.svelte-l7un4l{display:inline-flex;align-items:center;gap:10px;padding:7px 12px;min-height:var(--control-height);font-family:var(--sans);line-height:1.4;border-radius:var(--radius);background:var(--accent);color:var(--accent-ink);font-weight:600;white-space:nowrap;font-size:var(--text-body)}.open-app.svelte-l7un4l:hover{background:var(--accent-hover);text-decoration:none}.list-heading.svelte-l7un4l{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:18px}.list-heading.svelte-l7un4l h2:where(.svelte-l7un4l){font-size:var(--text-section);margin:0;font-weight:600}.service-search.svelte-l7un4l span:where(.svelte-l7un4l){display:block;color:var(--muted);font-size:var(--text-small);margin-bottom:4px}.service-search.svelte-l7un4l input:where(.svelte-l7un4l){background:var(--bg-2);width:220px}.service-heading.svelte-l7un4l strong:where(.svelte-l7un4l){font-size:var(--text-body)}.service-settings.svelte-l7un4l{grid-column:1 / -1;margin-top:14px;border-top:1px solid var(--border);padding-top:9px;color:var(--muted);font-size:var(--text-small)}.service-settings[open].svelte-l7un4l{padding-bottom:12px}.service-open.svelte-l7un4l{font-weight:600;padding:8px;white-space:nowrap}.service-actions.svelte-l7un4l{flex-wrap:wrap;justify-content:flex-end}.copy.svelte-l7un4l{background:transparent}.service-links.svelte-l7un4l{display:grid;gap:5px;margin-top:8px;min-width:0}.service-links.svelte-l7un4l a:where(.svelte-l7un4l){width:fit-content;max-width:100%;overflow-wrap:anywhere;font-size:var(--text-small)}.inline-error.svelte-l7un4l{grid-column:1 / -1}.settings-panel.svelte-l7un4l{width:100%;min-width:0}.settings-note.svelte-l7un4l{color:var(--muted);font-size:var(--text-small)}.config-list.svelte-l7un4l{width:100%;min-width:0;margin:20px 0 32px;border-top:1px solid var(--border)}.config-list.svelte-l7un4l>details:where(.svelte-l7un4l){min-width:0;border-bottom:1px solid var(--border);padding:16px 0}.config-list.svelte-l7un4l summary:where(.svelte-l7un4l){min-height:var(--control-height);display:flex;gap:20px;align-items:center;list-style:none}.config-list.svelte-l7un4l summary:where(.svelte-l7un4l)::-webkit-details-marker{display:none}.config-list.svelte-l7un4l summary:where(.svelte-l7un4l) strong:where(.svelte-l7un4l){min-width:80px;font:500 var(--text-label)/1.4 var(--sans)}.config-list.svelte-l7un4l summary:where(.svelte-l7un4l) span:where(.svelte-l7un4l){color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-small)}.config-list.svelte-l7un4l summary:where(.svelte-l7un4l) .edit-label:where(.svelte-l7un4l){margin-left:auto;flex-shrink:0;color:var(--text)}.settings-tools.svelte-l7un4l{padding:12px 0;color:var(--muted)}.settings-panel.svelte-l7un4l h2:where(.svelte-l7un4l){margin-top:24px}.project-health.svelte-l7un4l{display:flex;align-items:center;gap:12px;color:var(--muted)}.project-health.svelte-l7un4l:has(.running:where(.svelte-l7un4l)){color:var(--ok)}.project-address.svelte-l7un4l{display:flex;align-items:center;gap:12px;min-width:0;color:var(--muted);padding:24px 0;border-bottom:1px solid var(--border);margin-bottom:28px}.project-address.svelte-l7un4l a:where(.svelte-l7un4l){color:var(--muted);overflow-wrap:anywhere;font-size:var(--text-body)}.project-address.svelte-l7un4l button:where(.svelte-l7un4l),.manage-button.svelte-l7un4l{display:inline-flex;align-items:center;border:0;background:transparent;color:var(--muted);padding:10px}.manage-button.svelte-l7un4l{border-radius:var(--radius);text-decoration:none;border:1px solid var(--border);min-height:var(--control-height);gap:8px;padding:7px 12px;justify-content:center}.overview-row.svelte-l7un4l{display:grid;grid-template-columns:36px minmax(90px,1fr) minmax(60px,.8fr) minmax(90px,1fr);gap:16px;align-items:center;width:100%;font-size:var(--text-body)}.overview-row.svelte-l7un4l strong:where(.svelte-l7un4l){font-size:var(--text-label);font-weight:500;overflow-wrap:anywhere}.service-icon.svelte-l7un4l{width:36px;height:36px;display:grid;place-items:center;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-2);color:var(--muted)}.technical-name.svelte-l7un4l{color:var(--muted);font-weight:400;overflow-wrap:anywhere}.row-health.svelte-l7un4l{display:flex;align-items:center;gap:9px;color:var(--muted);font-weight:400}.row-health.svelte-l7un4l .status-dot:where(.svelte-l7un4l){background:currentColor;flex-shrink:0}.row-health.ready.svelte-l7un4l{color:var(--ok)}.row-health.errored.svelte-l7un4l{color:var(--err)}.row-health.svelte-l7un4l{text-transform:capitalize}.readiness-summary.svelte-l7un4l{color:var(--muted);font-size:var(--text-body);margin:24px 0 48px}.managing.svelte-l7un4l{margin-top:32px}.muted.svelte-l7un4l{color:var(--muted)}.profiles.svelte-l7un4l{padding:18px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-2)}.profiles[open].svelte-l7un4l summary:where(.svelte-l7un4l){margin-bottom:16px}@media(min-width:801px)and (max-width:1100px){.overview-row.svelte-l7un4l{grid-template-columns:48px minmax(0,1fr) auto;gap:16px}.technical-name.svelte-l7un4l{white-space:nowrap;display:none}.managing.svelte-l7un4l .service-card:where(.svelte-l7un4l){grid-template-columns:1fr}.managing.svelte-l7un4l .service-actions:where(.svelte-l7un4l){justify-content:flex-start;margin-top:16px}}@media(max-width:800px){.unavailable.svelte-l7un4l{align-items:stretch;flex-direction:column}.project-actions.svelte-l7un4l button:where(.svelte-l7un4l),.recovery-actions.svelte-l7un4l button:where(.svelte-l7un4l){flex:1}.service-grid.svelte-l7un4l{grid-template-columns:1fr}.service-card.svelte-l7un4l{grid-template-columns:1fr;padding:18px}.service-actions.svelte-l7un4l{justify-content:flex-start;margin-top:16px;gap:6px}.service-actions.svelte-l7un4l button:where(.svelte-l7un4l){padding:8px 10px}.launch-panel.svelte-l7un4l{align-items:flex-start;flex-direction:column;padding:20px}.list-heading.svelte-l7un4l{align-items:stretch;flex-direction:column}.service-search.svelte-l7un4l input:where(.svelte-l7un4l){width:100%}.project-actions.svelte-l7un4l{flex-wrap:wrap}.open-app.svelte-l7un4l{padding:7px 12px;font-size:var(--text-small)}.project-actions.svelte-l7un4l{gap:8px;flex-shrink:0}.manage-button.svelte-l7un4l{min-height:var(--control-height)}.project-address.svelte-l7un4l{padding:24px 0;margin-bottom:28px}.overview-row.svelte-l7un4l{grid-template-columns:40px minmax(0,1fr) auto;gap:12px}.service-icon.svelte-l7un4l{width:40px;height:40px}.technical-name.svelte-l7un4l{white-space:nowrap;display:none}.overview-row.svelte-l7un4l strong:where(.svelte-l7un4l){font-size:var(--text-label)}.row-health.svelte-l7un4l{font-size:var(--text-small)}.service-card.svelte-l7un4l{padding:18px 0}}.service-row.svelte-1h3c7zm{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:12px 24px;padding:20px 0;border-bottom:1px solid var(--border)}.service-info.svelte-1h3c7zm{display:grid;gap:5px;min-width:0}.service-heading.svelte-1h3c7zm{display:flex;align-items:center;gap:9px}strong.svelte-1h3c7zm{font-size:var(--text-label);font-weight:500}.status-dot.svelte-1h3c7zm{width:7px;height:7px;background:var(--muted);flex-shrink:0}.status-dot.running.svelte-1h3c7zm{background:var(--ok)}.service-status.svelte-1h3c7zm,.project-name.svelte-1h3c7zm{color:var(--muted);font-size:var(--text-small)}.service-status.svelte-1h3c7zm{margin-left:6px}.service-address.svelte-1h3c7zm{width:fit-content;max-width:100%;overflow-wrap:anywhere;color:var(--text)}.service-controls.svelte-1h3c7zm{display:flex;align-items:center;gap:8px;flex-wrap:wrap}button.svelte-1h3c7zm{display:inline-flex;justify-content:center;align-items:center;gap:7px}.action-error.svelte-1h3c7zm{grid-column:1 / -1;margin:0;color:var(--err);font-size:var(--text-small)}@media(max-width:900px){.service-row.svelte-1h3c7zm{grid-template-columns:minmax(0,1fr)}.service-controls.svelte-1h3c7zm{justify-content:flex-start}}.body-viewer.svelte-1eh4gal{min-width:0;margin:12px 0;border:1px solid var(--chrome-border)}.body-toolbar.svelte-1eh4gal{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:6px 10px;border-bottom:1px solid var(--chrome-border);color:var(--chrome-muted);font-size:.6875rem}button.svelte-1eh4gal{border:0;border-radius:0;background:transparent;color:var(--chrome-muted);min-height:28px;padding:3px 0;font-size:.6875rem}button.svelte-1eh4gal:hover{background:transparent;color:var(--chrome-text)}pre.svelte-1eh4gal{margin:0;padding:12px;max-height:440px;overflow:auto;white-space:pre;-moz-tab-size:2;tab-size:2;color:var(--chrome-text)}pre.svelte-1eh4gal,code.svelte-1eh4gal{font:.75rem/1.7 var(--mono)}.key.svelte-1eh4gal{color:#b6cbd7}.string.svelte-1eh4gal{color:#b6d598}.number.svelte-1eh4gal{color:#dfbc86}.literal.svelte-1eh4gal{color:#d4a8bb}p.svelte-1eh4gal{margin:8px 12px;color:var(--err)}.metadata.svelte-s74nzf{display:flex;flex-direction:column;margin:16px 0}details.svelte-s74nzf{min-width:0;border-bottom:1px solid var(--chrome-border)}summary.svelte-s74nzf{display:flex;align-items:center;justify-content:space-between;padding:10px 0;list-style:none;cursor:pointer;font-weight:600;color:var(--chrome-text)}summary.svelte-s74nzf::-webkit-details-marker{display:none}summary.svelte-s74nzf:after{content:"›";font-size:1rem}details[open].svelte-s74nzf>summary:where(.svelte-s74nzf):after{transform:rotate(90deg)}summary.svelte-s74nzf:hover{color:#fff}dl.svelte-s74nzf{margin:8px 0;font-family:var(--mono)}dl.svelte-s74nzf>div:where(.svelte-s74nzf){display:grid;grid-template-columns:minmax(90px,1fr) minmax(0,2fr);gap:12px;padding:5px 0;border-bottom:1px solid var(--chrome-border)}dt.svelte-s74nzf,dd.svelte-s74nzf{overflow-wrap:anywhere;white-space:pre-wrap}dd.svelte-s74nzf{margin:0;color:var(--chrome-muted)}h4.svelte-s74nzf{font-size:inherit;margin:12px 0 6px}p.svelte-s74nzf{color:var(--chrome-muted)}.requests-panel.svelte-tqqrb7{display:grid;grid-template-columns:minmax(0,1fr);flex:1;min-height:0;overflow:hidden;font:.75rem/1.6 var(--mono)}.requests-panel.inspecting.svelte-tqqrb7{grid-template-columns:minmax(260px,1fr) minmax(280px,.85fr)}.request-list.svelte-tqqrb7{min-height:0;min-width:0;overflow:auto}table.svelte-tqqrb7{font:inherit;width:100%;border-collapse:collapse;text-align:left;table-layout:fixed}th.svelte-tqqrb7,td.svelte-tqqrb7{padding:9px 14px;vertical-align:middle}th.svelte-tqqrb7{position:sticky;top:0;z-index:1;background:var(--chrome);color:var(--chrome-muted);font-size:.6875rem;font-weight:400;border-bottom:1px solid var(--chrome-border)}.numeric.svelte-tqqrb7{width:88px;text-align:right;white-space:nowrap;font-variant-numeric:tabular-nums}.service-column.svelte-tqqrb7{width:24%;color:var(--chrome-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.request-row.svelte-tqqrb7{cursor:pointer}.request-row.svelte-tqqrb7:nth-of-type(2n){background:#ffffff03}.request-row.svelte-tqqrb7:hover,.request-row.svelte-tqqrb7:focus-within{background:var(--bg-3)}.request-row.selected.svelte-tqqrb7{background:var(--bg-3);box-shadow:inset 2px 0 var(--accent)}button.svelte-tqqrb7{font:inherit;color:var(--chrome-text);background:transparent;border-radius:0}.request-cell.svelte-tqqrb7 button:where(.svelte-tqqrb7){display:flex;justify-content:flex-start;gap:10px;width:100%;min-height:24px;padding:0;border:0;text-align:left}.request-cell.svelte-tqqrb7 button:where(.svelte-tqqrb7):hover{background:transparent}.disclosure.svelte-tqqrb7{color:var(--chrome-muted);flex-shrink:0}.method.svelte-tqqrb7{width:6ch;flex-shrink:0;font-size:.6875rem;color:var(--chrome-muted)}.path.svelte-tqqrb7{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.status.svelte-tqqrb7{color:var(--chrome-text)}.status.failed.svelte-tqqrb7,.error.svelte-tqqrb7{color:var(--err)}.status.redirect.svelte-tqqrb7{color:var(--warn)}.duration.svelte-tqqrb7,.unit.svelte-tqqrb7{color:var(--chrome-muted)}.inspector.svelte-tqqrb7{min-width:0;min-height:0;overflow:auto;padding:0 20px 20px;background:var(--bg-2);border-left:1px solid var(--chrome-border);overflow-wrap:anywhere}.inspector.svelte-tqqrb7 header:where(.svelte-tqqrb7){position:sticky;top:0;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 0;margin-bottom:8px;background:var(--bg-2);z-index:2}.inspector.svelte-tqqrb7 h3:where(.svelte-tqqrb7){margin:0;font-size:inherit;font-weight:500}.close-inspector.svelte-tqqrb7{min-height:28px;width:28px;border:0;padding:0}.inspector.svelte-tqqrb7>strong:where(.svelte-tqqrb7){font-weight:500}.inspector.svelte-tqqrb7 p:where(.svelte-tqqrb7),.inspector.svelte-tqqrb7>span:where(.svelte-tqqrb7){color:var(--chrome-muted)}.replay.svelte-tqqrb7{border:1px solid var(--chrome-border);padding:6px 10px}.empty.svelte-tqqrb7{padding:28px 20px;color:var(--chrome-muted);text-align:center}.empty.svelte-tqqrb7 strong:where(.svelte-tqqrb7){color:var(--chrome-text);font-weight:500}.empty.svelte-tqqrb7 p:where(.svelte-tqqrb7){margin:6px 0 0}@media(max-width:900px){.requests-panel.inspecting.svelte-tqqrb7{grid-template-columns:minmax(0,1fr)}.inspecting.svelte-tqqrb7 .request-list:where(.svelte-tqqrb7){display:none}.inspector.svelte-tqqrb7{border-left:0}}@media(max-width:680px){th.svelte-tqqrb7,td.svelte-tqqrb7{padding:8px}.numeric.svelte-tqqrb7{width:65px}.service-column.svelte-tqqrb7{width:22%}.request-cell.svelte-tqqrb7 button:where(.svelte-tqqrb7){gap:5px}.inspector.svelte-tqqrb7{padding:16px}}.source-tabs-row.svelte-147la1m{display:flex;align-items:stretch;flex-shrink:0;min-width:0;border-bottom:1px solid var(--chrome-border)}.request-options.svelte-147la1m{position:relative;flex-shrink:0;margin-left:auto}.request-options.svelte-147la1m summary:where(.svelte-147la1m){height:100%;padding:8px 14px;border:0;border-left:1px solid var(--chrome-border);color:var(--chrome-muted)}.request-options-menu.svelte-147la1m{position:absolute;bottom:calc(100% + 8px);right:0;z-index:32;width:max-content;max-width:calc(100vw - 80px);padding:8px 12px;border:1px solid var(--chrome-border);background:var(--chrome);box-shadow:0 8px 24px #0005}.request-options-menu.svelte-147la1m label:where(.svelte-147la1m){display:flex;align-items:center;gap:8px;cursor:pointer;font-size:.75rem}.request-options-menu.svelte-147la1m input:where(.svelte-147la1m){flex-shrink:0}.logs-dock.svelte-147la1m{position:relative;display:flex;flex-direction:column;min-height:0;min-width:0;color:var(--chrome-text);background:var(--chrome);border-top:1px solid var(--chrome-border)}.resize-handle.svelte-147la1m{position:absolute;top:-4px;left:0;right:0;height:8px;cursor:ns-resize;touch-action:none;z-index:31}.resize-handle.svelte-147la1m:hover,.resize-handle.svelte-147la1m:focus-visible{background:var(--chrome-muted);outline:none}.logs-toolbar.svelte-147la1m{display:flex;align-items:center;gap:8px;padding:8px 14px;flex:0 0 auto}h2.svelte-147la1m{display:flex;align-items:center;gap:8px;margin:0 10px 0 0;font-size:.875rem;font-weight:500}button.svelte-147la1m,summary.svelte-147la1m{border-radius:0;display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:32px;color:var(--chrome-text);border-color:var(--chrome-border);font-size:.75rem;padding:5px 9px}button.svelte-147la1m:hover,summary.svelte-147la1m:hover{background:#ffffff0b;border-color:var(--chrome-muted)}button.svelte-147la1m:focus-visible,summary.svelte-147la1m:focus-visible,input.svelte-147la1m:focus-visible,.log-output.svelte-147la1m:focus-visible{outline:2px solid var(--chrome-muted);outline-offset:-2px}.source-picker.svelte-147la1m{position:relative;flex-shrink:0}summary.svelte-147la1m{list-style:none;border:1px solid var(--chrome-border)}summary.svelte-147la1m::-webkit-details-marker{display:none}.count.svelte-147la1m{color:var(--chrome-muted)}.source-options.svelte-147la1m{position:absolute;bottom:calc(100% + 8px);left:0;z-index:30;width:min(300px,calc(100vw - 40px));max-height:min(420px,calc(100dvh - var(--logs-height) - 64px));overflow:auto;border:1px solid var(--chrome-border);padding:8px;background:var(--chrome);box-shadow:0 8px 24px #0005}.source-group.svelte-147la1m{border:0;padding:0;margin:0;min-width:0}.source-group.svelte-147la1m+.source-group:where(.svelte-147la1m){border-top:1px solid var(--chrome-border);margin-top:8px;padding-top:8px}.source-group.svelte-147la1m legend:where(.svelte-147la1m){position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.source-options.svelte-147la1m .group-toggle:where(.svelte-147la1m){justify-content:space-between;gap:12px;font-weight:500}.group-toggle.svelte-147la1m>span:where(.svelte-147la1m):first-child{min-width:0;overflow-wrap:anywhere}.select-all.svelte-147la1m{display:inline-flex;align-items:center;gap:6px;flex-shrink:0;color:var(--chrome-muted);font-size:.6875rem;font-weight:400}.source-options.svelte-147la1m .service-choice:where(.svelte-147la1m){padding-left:22px}.source-options.svelte-147la1m input:where(.svelte-147la1m){flex-shrink:0}.source-options.svelte-147la1m label:where(.svelte-147la1m){display:flex;align-items:center;gap:9px;padding:6px 8px;cursor:pointer;font-size:.8125rem}.source-options.svelte-147la1m label:where(.svelte-147la1m):hover{background:#ffffff0b}.source-options.svelte-147la1m input:where(.svelte-147la1m){accent-color:var(--accent)}.filter.svelte-147la1m{display:flex;align-items:center;gap:7px;flex:1;min-width:110px;max-width:360px;color:var(--chrome-muted);border:1px solid var(--chrome-border);padding-left:9px}.filter.svelte-147la1m input:where(.svelte-147la1m){border-radius:0;min-width:0;width:100%;min-height:30px;border:0;padding:4px 8px 4px 0;font-size:.75rem;color:var(--chrome-text)}.filter.svelte-147la1m input:where(.svelte-147la1m)::placeholder{color:var(--chrome-muted)}.close.svelte-147la1m{margin-left:auto;width:32px;padding:0;flex-shrink:0;border:0;background:transparent;color:var(--chrome-muted)}.close.svelte-147la1m:hover{background:transparent;color:var(--chrome-text)}.source-tabs.svelte-147la1m{display:flex;gap:0;min-width:0;overflow-x:auto;padding:0 0 0 14px}.source-tab.svelte-147la1m,.source-tabs.svelte-147la1m .all-sources:where(.svelte-147la1m){display:flex;flex-shrink:0;align-items:center;border:0;border-right:1px solid var(--chrome-border);border-bottom:2px solid transparent;border-radius:0;color:var(--chrome-muted)}.source-tab.active.svelte-147la1m,.source-tabs.svelte-147la1m .all-sources.active:where(.svelte-147la1m){border-bottom-color:var(--accent);background:var(--bg-3);color:var(--chrome-text)}.source-tabs.svelte-147la1m button:where(.svelte-147la1m){border:0;border-radius:0;color:inherit;background:transparent;transform:none}.source-tabs.svelte-147la1m .all-sources:where(.svelte-147la1m){padding:8px 12px}.source-tabs.svelte-147la1m .source-select:where(.svelte-147la1m){align-self:stretch;padding:8px 8px 8px 12px}.source-tab.svelte-147la1m:hover,.source-tabs.svelte-147la1m .all-sources:where(.svelte-147la1m):hover{background:var(--bg-3);color:var(--chrome-text)}.source-tabs.svelte-147la1m .source-select:where(.svelte-147la1m):hover{background:transparent}.source-tabs.svelte-147la1m .clear-source:where(.svelte-147la1m){width:24px;min-height:28px;align-self:stretch;padding:0;margin:0;flex-shrink:0;color:var(--chrome-muted)}.source-tabs.svelte-147la1m .clear-source:where(.svelte-147la1m):hover:not(:disabled){color:var(--err);background:transparent}.source-tabs.svelte-147la1m .clear-source:where(.svelte-147la1m):active:not(:disabled){color:var(--err);background:transparent}.clear-source.svelte-147la1m:disabled{opacity:.4;cursor:wait}.project-name.svelte-147la1m{opacity:.7}.log-output.svelte-147la1m{flex:1;overflow:auto;min-height:0;padding:8px 0;font:.75rem/1.75 var(--mono)}.log-row.svelte-147la1m{display:grid;grid-template-columns:8ch minmax(12ch,22ch) 6ch minmax(0,1fr) 24px;gap:14px;padding:5px 14px;align-items:start}.log-row.single-source.svelte-147la1m{grid-template-columns:8ch 6ch minmax(0,1fr) 24px}.copy-line.svelte-147la1m{opacity:0;min-height:24px;width:24px;padding:0;border:0;color:var(--chrome-muted)}.log-row.svelte-147la1m:hover .copy-line:where(.svelte-147la1m),.copy-line.svelte-147la1m:focus-visible{opacity:1}.copy-line.svelte-147la1m:hover{background:transparent;color:var(--chrome-text)}.log-controls.svelte-147la1m{display:flex;gap:12px}.service-signal.svelte-147la1m{width:6px;height:6px;flex-shrink:0;background:var(--chrome-muted)}.service-signal.running.svelte-147la1m{background:var(--ok)}.service-signal.starting.svelte-147la1m{background:var(--warn)}.service-state.svelte-147la1m{margin-left:auto;color:var(--chrome-muted);font-size:.6875rem}@media(pointer:coarse){.copy-line.svelte-147la1m{opacity:1}}.log-row.svelte-147la1m:nth-child(2n){background:#ffffff03}.log-row.svelte-147la1m:hover{background:var(--bg-3)}time.svelte-147la1m{color:var(--chrome-muted);font-size:.6875rem;font-variant-numeric:tabular-nums;padding-top:1px}.source.svelte-147la1m{color:var(--chrome-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.6875rem;padding-top:1px}.stream.svelte-147la1m{color:var(--chrome-muted);font-size:.6875rem;padding-top:1px}.line.svelte-147la1m{min-width:0;white-space:pre-wrap;overflow-wrap:anywhere;-moz-tab-size:2;tab-size:2}.shared.svelte-147la1m .stream:where(.svelte-147la1m){color:var(--share)}.stderr.svelte-147la1m .stream:where(.svelte-147la1m){color:var(--warn)}@media(max-width:680px){.log-row.svelte-147la1m{grid-template-columns:8ch minmax(0,1fr) 6ch;gap:4px 10px;padding:7px 10px}.log-row.single-source.svelte-147la1m{grid-template-columns:8ch minmax(0,1fr)}.line.svelte-147la1m{grid-column:1 / -1}.copy-line.svelte-147la1m{grid-column:-2 / -1;justify-self:end;opacity:1}}.empty.svelte-147la1m{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100%;gap:4px;text-align:center;font-family:var(--sans)}.empty.svelte-147la1m strong:where(.svelte-147la1m){font-weight:500;font-size:.8125rem}.empty.svelte-147la1m span:where(.svelte-147la1m){color:var(--chrome-muted)}.history-error.svelte-147la1m{padding:8px 14px;color:var(--chrome-text);display:flex;align-items:center;gap:12px}.logs-status.svelte-147la1m{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-shrink:0;min-height:30px;padding:0 14px;border-top:1px solid var(--chrome-border);color:var(--chrome-muted);font-size:.6875rem}.logs-status.svelte-147la1m button:where(.svelte-147la1m){border:0;min-height:28px;font-size:.6875rem;padding:2px 5px}@media(max-width:680px){.logs-toolbar.svelte-147la1m{flex-wrap:wrap;gap:6px;padding:6px 10px}h2.svelte-147la1m{margin-right:0}.filter.svelte-147la1m{order:5;flex-basis:100%;max-width:none}.record-count.svelte-147la1m{display:none}}.layout.svelte-1n46o8q{position:relative;display:grid;grid-template-columns:var(--sidebar-width) minmax(0,1fr);grid-template-rows:minmax(0,1fr) auto auto;height:100dvh;min-height:0;background:var(--chrome)}.app-rail.svelte-1n46o8q{position:relative;grid-row:1 / 3;min-height:0;border-right:1px solid var(--chrome-border);display:flex;flex-direction:column;color:var(--chrome-text)}.brand.svelte-1n46o8q{letter-spacing:-.03em;display:flex;align-items:center;justify-content:space-between;padding:20px 16px;font:500 var(--text-section)/1.2 var(--sans);text-transform:none}.brand-identity.svelte-1n46o8q{display:flex;align-items:center;gap:10px;min-width:0}.brand-identity.svelte-1n46o8q img:where(.svelte-1n46o8q){display:block;flex-shrink:0}.brand-name.svelte-1n46o8q{font-size:var(--text-label)}.collapsed.svelte-1n46o8q .brand-name:where(.svelte-1n46o8q){display:none}.app-rail.svelte-1n46o8q nav:where(.svelte-1n46o8q){display:grid;gap:4px;padding:0 8px}.app-rail.svelte-1n46o8q :is(button:where(.svelte-1n46o8q),a:where(.svelte-1n46o8q)){display:flex;align-items:center;gap:10px;width:100%;min-height:44px;padding:12px 16px;font:500 var(--text-body)/1.4 var(--sans);text-transform:none;color:var(--chrome-muted);background:transparent;border:0;border-left:3px solid transparent;text-align:left}.app-rail.svelte-1n46o8q :where(.svelte-1n46o8q):is(button:where(.svelte-1n46o8q),a:where(.svelte-1n46o8q)):hover{color:var(--chrome-text);background:#ffffff0c}.app-rail.svelte-1n46o8q a:where(.svelte-1n46o8q){text-decoration:none}.app-rail.svelte-1n46o8q a.active:where(.svelte-1n46o8q){color:var(--accent);border-left-color:var(--accent);background:var(--selected)}.app-rail.svelte-1n46o8q :is(button:where(.svelte-1n46o8q),a:where(.svelte-1n46o8q)) svg{flex-shrink:0}main.svelte-1n46o8q{grid-column:2;grid-row:1;min-width:0;min-height:0;overflow:auto;display:flex;flex-direction:column;background:var(--bg);color:var(--text)}.logs-slot.svelte-1n46o8q{grid-column:2;grid-row:2;min-width:0}.statusbar.svelte-1n46o8q{grid-column:1 / -1;grid-row:3;min-height:40px;display:flex;align-items:center;gap:24px;border-top:1px solid var(--chrome-border);color:var(--chrome-muted);font-size:var(--text-small)}.runtime-label.svelte-1n46o8q{width:var(--sidebar-width);align-self:stretch;display:flex;align-items:center;flex-shrink:0;padding:0 20px;border-right:1px solid var(--chrome-border)}.collapsed.svelte-1n46o8q .runtime-label:where(.svelte-1n46o8q){width:64px;padding:0 8px;font-size:11px}.panel-controls.svelte-1n46o8q{display:flex;align-items:center;gap:8px;margin-left:auto;margin-right:16px}.statusbar.svelte-1n46o8q .logs-toggle:where(.svelte-1n46o8q){min-height:30px;color:var(--chrome-muted);border:0;background:transparent;text-decoration:underline;text-underline-offset:4px}.statusbar.svelte-1n46o8q .logs-toggle:where(.svelte-1n46o8q):hover,.statusbar.svelte-1n46o8q .logs-toggle[aria-expanded=true]:where(.svelte-1n46o8q){background:transparent;color:var(--chrome-text);text-decoration-thickness:2px}.connection.svelte-1n46o8q{display:flex;align-items:center;gap:14px}.connection.svelte-1n46o8q i:where(.svelte-1n46o8q){display:block;width:7px;height:7px;border-radius:50%;background:var(--muted)}.connection.connected.svelte-1n46o8q i:where(.svelte-1n46o8q){background:var(--orange)}.port-status.svelte-1n46o8q{padding-left:30px;border-left:1px solid var(--chrome-border)}.catalog-error.svelte-1n46o8q{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 24px;color:var(--err);background:var(--bg-2)}h2.svelte-1n46o8q{font:500 var(--text-section)/1.4 var(--sans)}.all-service-grid.svelte-1n46o8q{display:grid;margin-top:14px}.all-service-card.svelte-1n46o8q{display:grid;justify-content:stretch;min-width:0;padding:18px 16px;gap:6px;text-align:left;white-space:normal;background:transparent;border:0;border-bottom:1px solid var(--border);border-radius:0}.all-service-card.svelte-1n46o8q:hover{background:var(--bg-3)}.service-heading.svelte-1n46o8q strong:where(.svelte-1n46o8q){font:500 var(--text-label)/1.4 var(--sans)}.service-heading.svelte-1n46o8q{display:flex;flex-wrap:wrap;align-items:center;gap:8px}.service-status.svelte-1n46o8q{margin-left:auto;color:var(--muted);font:var(--text-small)/1.4 var(--sans);text-transform:none}.project-name.svelte-1n46o8q{color:var(--muted);font-size:var(--text-small)}.empty-state.svelte-1n46o8q{width:100%;padding:48px 32px;text-align:center;color:var(--muted)}.load-state.svelte-1n46o8q{display:block;color:var(--muted)}.empty-state.svelte-1n46o8q h2:where(.svelte-1n46o8q),.load-state.svelte-1n46o8q h1:where(.svelte-1n46o8q){color:var(--text)}.global-settings.svelte-1n46o8q{font-size:var(--text-body);line-height:1.8}.global-settings.svelte-1n46o8q h3:where(.svelte-1n46o8q){margin-top:40px;font:500 var(--text-section)/1.4 var(--sans)}.global-settings.svelte-1n46o8q label:where(.svelte-1n46o8q){display:flex;gap:12px;align-items:center}.forwarder-error.svelte-1n46o8q{color:var(--err)}.collapsed.svelte-1n46o8q{grid-template-columns:64px minmax(0,1fr)}.app-rail.svelte-1n46o8q .collapse-toggle:where(.svelte-1n46o8q){width:36px;min-height:44px;flex-shrink:0;padding:4px;border:0;justify-content:center}.collapsed.svelte-1n46o8q .brand:where(.svelte-1n46o8q){flex-direction:column;gap:12px;padding:16px 4px}.collapsed.svelte-1n46o8q nav:where(.svelte-1n46o8q) a:where(.svelte-1n46o8q){justify-content:center;padding:12px 0}.collapsed.svelte-1n46o8q nav:where(.svelte-1n46o8q) a:where(.svelte-1n46o8q) span:where(.svelte-1n46o8q){display:none}.palette-trigger.svelte-1n46o8q kbd:where(.svelte-1n46o8q){margin-left:auto;font:11px var(--mono)}.collapsed.svelte-1n46o8q .palette-trigger:where(.svelte-1n46o8q){justify-content:center;padding:12px 0}.collapsed.svelte-1n46o8q .palette-trigger:where(.svelte-1n46o8q) span:where(.svelte-1n46o8q),.collapsed.svelte-1n46o8q .palette-trigger:where(.svelte-1n46o8q) kbd:where(.svelte-1n46o8q){display:none}.app-rail.svelte-1n46o8q .clean-urls-warning:where(.svelte-1n46o8q){margin-top:auto;border:0;border-top:1px solid var(--chrome-border);padding:16px 20px;color:#d1b78d;flex-shrink:0;justify-content:flex-start;white-space:normal}.app-rail.svelte-1n46o8q .clean-urls-warning:where(.svelte-1n46o8q):hover{color:#ead5ac;background:#ffffff0c;text-decoration:none}.clean-urls-warning.svelte-1n46o8q strong:where(.svelte-1n46o8q),.clean-urls-warning.svelte-1n46o8q small:where(.svelte-1n46o8q){display:block}.clean-urls-warning.svelte-1n46o8q strong:where(.svelte-1n46o8q){font-size:var(--text-small);font-weight:500}.clean-urls-warning.svelte-1n46o8q small:where(.svelte-1n46o8q){color:var(--chrome-muted);font-size:var(--text-small)}.collapsed.svelte-1n46o8q .clean-urls-warning:where(.svelte-1n46o8q){justify-content:center;padding:16px 0}.collapsed.svelte-1n46o8q .clean-urls-warning:where(.svelte-1n46o8q) span:where(.svelte-1n46o8q){display:none}.project-shortcuts.svelte-1n46o8q{overflow:auto;padding:24px 8px}.project-shortcuts.svelte-1n46o8q p:where(.svelte-1n46o8q){padding:0 12px;color:var(--chrome-muted);font:var(--text-small)/1.4 var(--sans);text-transform:none}.project-shortcuts.svelte-1n46o8q button:where(.svelte-1n46o8q){text-transform:none;overflow:hidden;text-overflow:ellipsis;display:block;min-height:36px;padding:8px 12px}.project-shortcuts.svelte-1n46o8q button.current:where(.svelte-1n46o8q){color:var(--accent);background:var(--selected)}.sidebar-resizer.svelte-1n46o8q{position:absolute;right:-3px;top:0;bottom:0;width:7px;cursor:col-resize;touch-action:none;z-index:10}.sidebar-resizer.svelte-1n46o8q:hover,.sidebar-resizer.svelte-1n46o8q:focus-visible{background:var(--orange)}.all-search.svelte-1n46o8q{display:flex;align-items:center;gap:16px;margin-top:24px;color:var(--muted)}.all-search.svelte-1n46o8q input:where(.svelte-1n46o8q){width:min(360px,100%)}@media(max-width:760px){.layout.svelte-1n46o8q,.layout.collapsed.svelte-1n46o8q{grid-template-columns:64px minmax(0,1fr)}.layout.svelte-1n46o8q:not(.collapsed) .app-rail:where(.svelte-1n46o8q){position:absolute;inset:0 auto 0 0;width:min(var(--sidebar-width),calc(100vw - 64px));z-index:20;background:var(--chrome)}.sidebar-resizer.svelte-1n46o8q{display:none}.statusbar.svelte-1n46o8q{gap:10px;font-size:11px}.runtime-label.svelte-1n46o8q{width:64px;padding:0 8px;font-size:11px}.panel-controls.svelte-1n46o8q{gap:0;margin-right:8px}.statusbar.svelte-1n46o8q .logs-toggle:where(.svelte-1n46o8q){padding:4px 8px}.port-status.svelte-1n46o8q{padding-left:10px}.connection.svelte-1n46o8q{gap:6px}.connection.svelte-1n46o8q i:where(.svelte-1n46o8q){width:6px;height:6px}.all-search.svelte-1n46o8q{align-items:stretch;flex-direction:column;gap:6px}}