localdeck 1.0.2 → 1.1.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/CHANGELOG.md +43 -0
- package/README.md +15 -0
- package/dashboard/assets/index-CmTkywPB.css +1 -0
- package/dashboard/assets/index-DqZDYOkh.js +20 -0
- package/dashboard/index.html +2 -2
- package/dist/{index-eyb4mz5b.js → index-n4sq4z00.js} +486 -88
- package/dist/index.js +32 -9
- package/dist/{mcp-4dnef2t7.js → mcp-arq8ad4g.js} +6 -2
- package/dist/update-gj01sqte.js +127 -0
- package/docs/commands.md +12 -0
- package/docs/configuration.md +6 -1
- package/docs/dashboard.md +8 -2
- package/package.json +1 -1
- package/dashboard/assets/index-78KgNO32.js +0 -20
- package/dashboard/assets/index-BSKA3LqW.css +0 -1
package/dist/index.js
CHANGED
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
loadProject,
|
|
18
18
|
loadProjectFile,
|
|
19
19
|
processTree,
|
|
20
|
+
resolveProjectSecrets,
|
|
21
|
+
secretOutput,
|
|
20
22
|
serviceKey,
|
|
21
23
|
shellCommand,
|
|
22
24
|
startDaemon,
|
|
@@ -27,7 +29,7 @@ import {
|
|
|
27
29
|
stripAnsi,
|
|
28
30
|
waitForReadiness,
|
|
29
31
|
wrapper_default
|
|
30
|
-
} from "./index-
|
|
32
|
+
} from "./index-n4sq4z00.js";
|
|
31
33
|
|
|
32
34
|
// src/index.ts
|
|
33
35
|
import { spawn as spawn2 } from "node:child_process";
|
|
@@ -119,15 +121,20 @@ async function startService(opts, overrides = {}) {
|
|
|
119
121
|
throw new Error(`working directory does not exist or is not a directory: ${cwd}`);
|
|
120
122
|
}
|
|
121
123
|
const daemon = await dependencies.ensureDaemon();
|
|
122
|
-
const upstreamPort = opts.upstreamPort ?? await dependencies.getFreePort();
|
|
123
|
-
const paint = opts.color ?? c.cyan;
|
|
124
|
-
const tag = opts.prefix ? paint(`[${opts.name}] `) : "";
|
|
125
124
|
const project = opts.project ?? {
|
|
126
125
|
id: "standalone",
|
|
127
126
|
name: "standalone",
|
|
128
127
|
slug: "standalone"
|
|
129
128
|
};
|
|
130
|
-
|
|
129
|
+
let secretEnv = {};
|
|
130
|
+
const resolveSecrets = dependencies.resolveProjectSecrets ?? resolveProjectSecrets;
|
|
131
|
+
if (opts.secrets?.length && project.id !== "standalone")
|
|
132
|
+
secretEnv = resolveSecrets(project.id, opts.secrets);
|
|
133
|
+
const upstreamPort = opts.upstreamPort ?? await dependencies.getFreePort();
|
|
134
|
+
const paint = opts.color ?? c.cyan;
|
|
135
|
+
const tag = opts.prefix ? paint(`[${opts.name}] `) : "";
|
|
136
|
+
let commandEnv = await expandRunEnvironment(opts.env, opts.after ?? [], project.id, daemon.apiPort, dependencies.fetch);
|
|
137
|
+
commandEnv = { ...commandEnv, ...secretEnv };
|
|
131
138
|
const ws = dependencies.createWebSocket(`ws://127.0.0.1:${daemon.apiPort}/api/attach`, {
|
|
132
139
|
headers: { authorization: `Bearer ${daemon.controlToken}` }
|
|
133
140
|
});
|
|
@@ -160,14 +167,16 @@ async function startService(opts, overrides = {}) {
|
|
|
160
167
|
});
|
|
161
168
|
const argv = opts.command.map((a) => a.replaceAll("{port}", String(upstreamPort)));
|
|
162
169
|
const database = service.serviceType !== undefined && service.serviceType !== "http";
|
|
163
|
-
|
|
170
|
+
let env = {
|
|
164
171
|
...process.env,
|
|
165
172
|
...commandEnv,
|
|
173
|
+
...secretEnv,
|
|
166
174
|
PORT: String(upstreamPort),
|
|
167
175
|
LOCALDECK_PORT: String(service.stablePort),
|
|
168
176
|
LOCALDECK_SERVICE: service.name,
|
|
169
177
|
...database ? {} : { LOCALDECK_URL: `http://localhost:${service.stablePort}` }
|
|
170
178
|
};
|
|
179
|
+
const redactionValues = new Set(Object.values(secretEnv));
|
|
171
180
|
if (database) {
|
|
172
181
|
console.log(`${tag}${c.bold("LocalDeck")} ${c.dim("›")} ${paint(service.name)} ${c.dim("→")} ${c.green(`database localhost:${service.upstreamPort}`)} ${c.dim("(published port · no HTTP proxy)")}`);
|
|
173
182
|
} else {
|
|
@@ -177,7 +186,8 @@ async function startService(opts, overrides = {}) {
|
|
|
177
186
|
const posix = process.platform !== "win32";
|
|
178
187
|
let child;
|
|
179
188
|
const pipe = (stream, name, out) => {
|
|
180
|
-
stream.
|
|
189
|
+
const output = redactionValues.size ? stream.pipe(secretOutput(redactionValues)) : stream;
|
|
190
|
+
output.on("data", (chunk) => {
|
|
181
191
|
if (opts.prefix) {
|
|
182
192
|
const text = stripAnsi(chunk.toString());
|
|
183
193
|
recentOutput = (recentOutput + text).slice(-16000);
|
|
@@ -185,7 +195,7 @@ async function startService(opts, overrides = {}) {
|
|
|
185
195
|
} else
|
|
186
196
|
out.write(chunk);
|
|
187
197
|
});
|
|
188
|
-
streamLines(
|
|
198
|
+
streamLines(output, (line) => {
|
|
189
199
|
for (const part of splitAttachLogLine(stripAnsi(line)))
|
|
190
200
|
send({ type: "log", stream: name, line: part });
|
|
191
201
|
});
|
|
@@ -294,6 +304,13 @@ async function startService(opts, overrides = {}) {
|
|
|
294
304
|
console.error(`${tag}${c.yellow("LocalDeck: lost connection to daemon; the stable port is no longer proxied. Process keeps running.")}`);
|
|
295
305
|
});
|
|
296
306
|
function launch() {
|
|
307
|
+
if (retryAttempts > 0 && opts.secrets?.length && project.id !== "standalone") {
|
|
308
|
+
secretEnv = resolveSecrets(project.id, opts.secrets);
|
|
309
|
+
for (const value of Object.values(secretEnv))
|
|
310
|
+
redactionValues.add(value);
|
|
311
|
+
commandEnv = { ...commandEnv, ...secretEnv };
|
|
312
|
+
env = { ...process.env, ...commandEnv, PORT: String(upstreamPort), LOCALDECK_PORT: String(service.stablePort), LOCALDECK_SERVICE: service.name, ...database ? {} : { LOCALDECK_URL: `http://localhost:${service.stablePort}` } };
|
|
313
|
+
}
|
|
297
314
|
leaderExit = undefined;
|
|
298
315
|
cleanupPromise = undefined;
|
|
299
316
|
finalizing = false;
|
|
@@ -832,6 +849,7 @@ ${c.bold("Usage")}
|
|
|
832
849
|
localdeck daemon status|start|stop|run
|
|
833
850
|
localdeck --help | --h | -h show help
|
|
834
851
|
localdeck --version | --v | -v show the installed version
|
|
852
|
+
localdeck --update update the global npm install and restart the daemon
|
|
835
853
|
|
|
836
854
|
${c.bold("How it works")}
|
|
837
855
|
LocalDeck holds a stable port for each service (e.g. 4000) and proxies to
|
|
@@ -876,12 +894,16 @@ async function main(argv, overrides = {}) {
|
|
|
876
894
|
case "--help":
|
|
877
895
|
console.log(HELP);
|
|
878
896
|
return;
|
|
897
|
+
case "--update":
|
|
898
|
+
if (rest.length)
|
|
899
|
+
throw new Error("Usage: localdeck --update");
|
|
900
|
+
return (await import("./update-gj01sqte.js")).updateLocalDeck();
|
|
879
901
|
case "up":
|
|
880
902
|
return cmdUp(rest, dependencies);
|
|
881
903
|
case "init":
|
|
882
904
|
return cmdInit(rest, dependencies);
|
|
883
905
|
case "mcp":
|
|
884
|
-
return (await import("./mcp-
|
|
906
|
+
return (await import("./mcp-arq8ad4g.js")).runMcp(rest);
|
|
885
907
|
case "doctor":
|
|
886
908
|
return cmdDoctor(rest);
|
|
887
909
|
case "forward":
|
|
@@ -951,6 +973,7 @@ function toRunOptions(p, project, remembered, extra = {}) {
|
|
|
951
973
|
after: p.after,
|
|
952
974
|
ready: p.ready,
|
|
953
975
|
...p.env ? { env: p.env } : {},
|
|
976
|
+
...p.secrets ? { secrets: p.secrets } : {},
|
|
954
977
|
...p.serviceType ? { serviceType: p.serviceType } : {},
|
|
955
978
|
...extra
|
|
956
979
|
};
|
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
__toESM,
|
|
7
7
|
ensureDaemon,
|
|
8
8
|
environmentValues,
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
listProjectSecrets,
|
|
10
|
+
redactDiagnostic,
|
|
11
|
+
resolveProjectSecrets
|
|
12
|
+
} from "./index-n4sq4z00.js";
|
|
11
13
|
|
|
12
14
|
// ../../node_modules/.bun/ajv@8.20.0/node_modules/ajv/dist/compile/codegen/code.js
|
|
13
15
|
var require_code = __commonJS((exports) => {
|
|
@@ -34008,6 +34010,8 @@ function createMcpServer(options) {
|
|
|
34008
34010
|
scope(projectId);
|
|
34009
34011
|
const details = await api2.project(projectId);
|
|
34010
34012
|
const directories = new Set(details.directory ? [details.directory] : []);
|
|
34013
|
+
const stored = options.projectSecrets ? options.projectSecrets(details.id) : /^[a-f0-9]{16}$/.test(details.id) ? resolveProjectSecrets(details.id, listProjectSecrets(details.id)) : {};
|
|
34014
|
+
secrets.push(...Object.values(stored));
|
|
34011
34015
|
for (const command of details.commands) {
|
|
34012
34016
|
for (const [name, value] of Object.entries(command.env ?? {})) {
|
|
34013
34017
|
if (/password|secret|token|api[_-]?key|credential/i.test(name))
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Api,
|
|
3
|
+
STATE_FILE,
|
|
4
|
+
findDaemon
|
|
5
|
+
} from "./index-n4sq4z00.js";
|
|
6
|
+
|
|
7
|
+
// src/update.ts
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
function installationPrefix(packageRoot, globalRoot, platform = process.platform) {
|
|
13
|
+
const paths = platform === "win32" ? path.win32 : path.posix;
|
|
14
|
+
const root = paths.normalize(packageRoot);
|
|
15
|
+
const modules = paths.dirname(root);
|
|
16
|
+
const equal = (a, b) => platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
17
|
+
if (paths.basename(root) !== "localdeck" || !equal(paths.basename(modules), "node_modules")) {
|
|
18
|
+
throw new Error("--update requires a global npm installation. Source checkouts and linked development builds must be updated manually.");
|
|
19
|
+
}
|
|
20
|
+
if (equal(modules, paths.normalize(globalRoot))) {
|
|
21
|
+
return platform === "win32" ? paths.dirname(modules) : paths.dirname(paths.dirname(modules));
|
|
22
|
+
}
|
|
23
|
+
if (platform !== "win32" && paths.basename(paths.dirname(modules)) === "lib")
|
|
24
|
+
return paths.dirname(paths.dirname(modules));
|
|
25
|
+
throw new Error("This is not the active global npm installation. Update it with the package manager and prefix used to install it.");
|
|
26
|
+
}
|
|
27
|
+
function npmCommand() {
|
|
28
|
+
const candidates = [
|
|
29
|
+
path.resolve(path.dirname(process.execPath), "node_modules/npm/bin/npm-cli.js"),
|
|
30
|
+
path.resolve(path.dirname(process.execPath), "../lib/node_modules/npm/bin/npm-cli.js")
|
|
31
|
+
];
|
|
32
|
+
for (const directory of (process.env.PATH ?? "").split(path.delimiter)) {
|
|
33
|
+
if (!directory)
|
|
34
|
+
continue;
|
|
35
|
+
candidates.push(path.join(directory, "node_modules/npm/bin/npm-cli.js"));
|
|
36
|
+
try {
|
|
37
|
+
const executable = fs.realpathSync(path.join(directory, "npm"));
|
|
38
|
+
if (path.basename(executable) === "npm-cli.js")
|
|
39
|
+
candidates.push(executable);
|
|
40
|
+
} catch {}
|
|
41
|
+
}
|
|
42
|
+
const cli = candidates.find((candidate) => fs.existsSync(candidate));
|
|
43
|
+
if (!cli)
|
|
44
|
+
throw new Error("npm was not found. Install Node.js with npm, then run localdeck --update again.");
|
|
45
|
+
return [process.execPath, cli];
|
|
46
|
+
}
|
|
47
|
+
async function execute(command, capture = false) {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const child = spawn(command[0], command.slice(1), { stdio: capture ? ["ignore", "pipe", "inherit"] : "inherit", shell: false });
|
|
50
|
+
let output = "";
|
|
51
|
+
child.stdout?.on("data", (chunk) => {
|
|
52
|
+
output += chunk.toString();
|
|
53
|
+
});
|
|
54
|
+
child.once("error", reject);
|
|
55
|
+
child.once("close", (code, signal) => code === 0 ? resolve(output.trim()) : reject(new Error(`Command failed (${signal ?? `exit ${code}`}).`)));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async function applyUpdate(steps) {
|
|
59
|
+
await steps.install();
|
|
60
|
+
const version = await steps.validate();
|
|
61
|
+
await steps.stop();
|
|
62
|
+
await steps.start();
|
|
63
|
+
return version;
|
|
64
|
+
}
|
|
65
|
+
async function updateLocalDeck() {
|
|
66
|
+
const root = fs.realpathSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."));
|
|
67
|
+
const npm = npmCommand();
|
|
68
|
+
const globalRoot = await execute([...npm, "root", "--global"], true);
|
|
69
|
+
const prefix = installationPrefix(root, globalRoot);
|
|
70
|
+
const bin = path.join(root, "bin/localdeck.js");
|
|
71
|
+
console.log(`Updating LocalDeck from npm in ${prefix}…`);
|
|
72
|
+
console.log("After installation, the daemon will restart. Managed services and public shares will stop; start them again when ready.");
|
|
73
|
+
let installedVersion = "";
|
|
74
|
+
const version = await applyUpdate({
|
|
75
|
+
install: async () => {
|
|
76
|
+
try {
|
|
77
|
+
await execute([...npm, "install", "--global", "--prefix", prefix, "localdeck@latest", "--registry=https://registry.npmjs.org/"]);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw new Error(`npm update failed. The daemon was not restarted. Check the npm error above; the installation prefix must be writable. ${error instanceof Error ? error.message : error}`);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
validate: async () => {
|
|
83
|
+
const version2 = await execute([process.execPath, bin, "--version"], true);
|
|
84
|
+
if (!/^\d+\.\d+\.\d+(?:[-+][\w.-]+)?$/.test(version2))
|
|
85
|
+
throw new Error("The updated CLI could not be verified. The daemon was not restarted.");
|
|
86
|
+
installedVersion = version2;
|
|
87
|
+
return version2;
|
|
88
|
+
},
|
|
89
|
+
stop: async () => {
|
|
90
|
+
const running = await findDaemon();
|
|
91
|
+
if (!running)
|
|
92
|
+
return;
|
|
93
|
+
await new Api(running.apiPort, globalThis.fetch, 30000).shutdown();
|
|
94
|
+
const deadline = Date.now() + 30000;
|
|
95
|
+
const ownsState = () => {
|
|
96
|
+
try {
|
|
97
|
+
return JSON.parse(fs.readFileSync(STATE_FILE, "utf8")).pid === running.pid;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (error.code === "ENOENT")
|
|
100
|
+
return false;
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
while (ownsState()) {
|
|
105
|
+
if (Date.now() >= deadline)
|
|
106
|
+
throw new Error("Package updated, but the daemon did not stop within 30 seconds. Restart it manually.");
|
|
107
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
start: async () => {
|
|
111
|
+
try {
|
|
112
|
+
await execute([process.execPath, bin, "daemon", "start"]);
|
|
113
|
+
const running = await findDaemon();
|
|
114
|
+
if (running?.version !== installedVersion)
|
|
115
|
+
throw new Error("Unexpected daemon version");
|
|
116
|
+
} catch {
|
|
117
|
+
throw new Error("Package updated, but the daemon could not restart. Run localdeck daemon start to retry.");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
console.log(`LocalDeck ${version} installed. The daemon has restarted.`);
|
|
122
|
+
}
|
|
123
|
+
export {
|
|
124
|
+
updateLocalDeck,
|
|
125
|
+
installationPrefix,
|
|
126
|
+
applyUpdate
|
|
127
|
+
};
|
package/docs/commands.md
CHANGED
|
@@ -92,3 +92,15 @@ Child services receive `PORT` (upstream), `LOCALDECK_PORT` (stable/published por
|
|
|
92
92
|
## MCP client connection
|
|
93
93
|
|
|
94
94
|
`localdeck mcp` runs the stdio MCP server for service inspection, controls, recorded GET/HEAD replay, and quick sharing. `--project <id>` restricts every tool to one remembered project; `--help` prints usage without connecting. A tool call can start the daemon, but connecting never starts application services. Closing the client leaves services running. See [MCP setup and every tool](mcp.md).
|
|
95
|
+
|
|
96
|
+
## Update LocalDeck
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
localdeck --update
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Available starting with 1.0.3. Installs the latest public npm release into the current global npm prefix, verifies the updated CLI, and restarts the daemon. Restarting stops daemon-managed services and public shares. Start services again afterward; terminal sessions may need to be rerun to reconnect.
|
|
103
|
+
|
|
104
|
+
A failed installation leaves the daemon running. The npm prefix must be writable; LocalDeck never invokes sudo. Source checkouts, linked builds, local installs, npx caches, and unsupported package-manager layouts must be updated manually. Custom Unix prefixes are preserved. On Windows the installation must match npm's active global root.
|
|
105
|
+
|
|
106
|
+
Older versions without this command can upgrade with `npm install -g localdeck@latest` using their existing npm prefix.
|
package/docs/configuration.md
CHANGED
|
@@ -22,7 +22,12 @@ JSON at the project root. `localdeck init` generates one from `package.json`; ed
|
|
|
22
22
|
- `after` — optional, one service name or an array. LocalDeck starts those prerequisites first and waits until they are ready. `localdeck web` and `localdeck up frontend` automatically include transitive prerequisites.
|
|
23
23
|
- `ready` — optional advanced readiness check. Without it, LocalDeck waits for its ownership-verified upstream port. Use `"/health"` for an HTTP endpoint, or one of the advanced objects below.
|
|
24
24
|
- `serviceType` — `http` (default), `postgres`, `redis`, or `compose`. Database services require `upstream`, use that published port directly, and cannot set an HTTP proxy `port`. They have no browser/share links: LocalDeck does not proxy PostgreSQL or Redis traffic.
|
|
25
|
-
- `env` — optional environment overrides. `PORT` and `LOCALDECK_*` are reserved. Use `{service:api:url}` or `{service:database:port}` to reference a service explicitly listed in `after`. Database port references use the actual published port; database URL references are rejected.
|
|
25
|
+
- `env` — optional environment overrides. `PORT` and `LOCALDECK_*` are reserved. Use `{service:api:url}` or `{service:database:port}` to reference a service explicitly listed in `after`. Database port references use the actual published port; database URL references are rejected.
|
|
26
|
+
- `secrets` — optional list of environment names whose values are managed in Project → Secrets. Names are declared here, while values are stored outside the project at `$LOCALDECK_HOME/secrets/<project-id>.json` (by default `~/.localdeck/secrets/`). Values are plaintext on this computer and never written to `.localdeck`.
|
|
27
|
+
|
|
28
|
+
For example: `{ "name": "web", "run": "npm run dev", "secrets": ["CLOUDFLARE_API", "CLOUDFLARE_ID"] }`. Each machine needs its own secret values. A missing value blocks launch with instructions to add it. Saved changes apply the next time the service starts, including automatic retries; editing a value never restarts a running service.
|
|
29
|
+
|
|
30
|
+
Secrets are stored in plaintext on this computer, outside your project. They are not encrypted. Anyone with access to the file can read them. Values persist across daemon restarts, updates, and reboots. File permissions restrict access where supported; they do not make the file readable only by LocalDeck.
|
|
26
31
|
|
|
27
32
|
The dashboard shows each relationship as **Starts after** and lets you edit it with checkboxes—no graph terminology or manual JSON is required. Missing names, self-dependencies, and cycles are rejected before the file changes.
|
|
28
33
|
|
package/docs/dashboard.md
CHANGED
|
@@ -26,9 +26,11 @@ A nickname such as `platform` produces `http://platform.web.localhost:7777`. Wit
|
|
|
26
26
|
|
|
27
27
|
Service configuration fills the available width. Edit stable and upstream ports, TCP or HTTP readiness, timing, and startup dependencies. Changes are validated and saved atomically, and apply to running services after restart. Commands, working directories, environment values, and command-based readiness remain file-edited.
|
|
28
28
|
|
|
29
|
+
The Secrets button beside Settings on the project page opens a dedicated Secrets page at `/projects/<project-id>/secrets`. It lists declared names and whether each value is configured. Use Add secret, Replace, or Set value to open the editor popover; click outside or press Escape to dismiss it. Remove deletes a saved value. Password fields start empty; saved values are never shown again. Secrets are stored in plaintext on this computer, outside your project. They are not encrypted. Anyone with access to the file can read them. Changes apply when the service next starts. Secret values are per machine and must be configured separately on each machine.
|
|
30
|
+
|
|
29
31
|
## Logs
|
|
30
32
|
|
|
31
|
-
Choose **Logs** or **View logs** to open that service's tab in the bottom panel. Select additional sources from the Services menu; **All selected** combines their output chronologically. Filter retained output or follow new lines.
|
|
33
|
+
Choose **Logs** or **View logs** to open that service's tab in the bottom panel. Select additional sources from the Services menu; **All selected** combines their output chronologically. Filter retained output or follow new lines. Use the trash icon beside a service tab to clear that service’s logs. Clearing keeps the service running and new output continues to appear.
|
|
32
34
|
|
|
33
35
|
Drag the panel's top edge to resize it, or focus the edge and use the arrow keys or Home/End. Its height is saved in the browser. The panel stays available when navigating between pages.
|
|
34
36
|
|
|
@@ -68,7 +70,11 @@ Windows CI covers native detection and script syntax. Administrator installation
|
|
|
68
70
|
|
|
69
71
|
Project diagnostics include configuration, readiness errors, and recent logs. Environment values and recognized credential formats are redacted; review the preview because application output may still contain private data. Nothing is uploaded automatically.
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
Successful asset and development-module requests are hidden by default. Enable **Show assets & dev traffic** to include them; this preference is saved in the browser. Failed requests, page navigations, and API calls remain visible. Filtering affects the dashboard view, not capture.
|
|
74
|
+
|
|
75
|
+
Choose **View requests** on a service, then expand a request row to inspect its method, path, status, timing, size, and local/share source. Details and replay responses expand directly below the selected row. Click the request again to collapse it. Service tabs and filtering are available in this view. The trash icon beside each service tab clears only its requests. Clears apply to connected dashboards and survive refreshes and daemon restarts. **Replay request** sends eligible GET/HEAD requests and displays the new response body. New HTTP requests include request and response headers, request cookies, Set-Cookie values, and query parameters. Headers retain duplicate entries and are capped at 100 entries or 16 KiB per direction. These details, including cookie and authorization values, are stored in local request history. Older records cannot recover headers that were not captured. New HTTP requests also retain original request and response body previews, limited to 16 KiB per direction. Text is shown as UTF-8; binary or undecodable compressed content is shown as Base64. Complete gzip, deflate and Brotli bodies are decoded within the same preview limit. Expand **Response** to load the full captured response. Full bodies use a shared 64 MiB cache, which evicts older entries as it fills and resets when the daemon restarts. The inspector identifies expired bodies and incomplete transfers. Original previews remain in saved history. Older records cannot recover bodies that were not captured. Request inspection is also available through CLI/API/MCP. Recorded GET/HEAD replay sends a new request to the current local upstream, without copying cookies, authorization, or request bodies. It does not follow redirects and has bounded response size and duration.
|
|
76
|
+
|
|
77
|
+
Traffic must pass through a LocalDeck service URL to be captured. Calls directly to Wrangler at `localhost:8787`, or through Worker service bindings, bypass the proxy. Configure your frontend’s API base URL to use the API service’s LocalDeck URL.
|
|
72
78
|
|
|
73
79
|
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.
|
|
74
80
|
|