@kmugalkhod/lightcode 0.13.0 → 0.14.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/README.md +5 -3
- package/assets/web/{index-21aq5q6q.css → index-2dshmdgm.css} +1 -1
- package/assets/web/index-x9zna9dg.js +325 -0
- package/assets/web/index.html +2 -2
- package/cli.js +8 -2
- package/package.json +1 -1
- package/server.js +223 -69
- package/assets/web/index-j5mzdh93.js +0 -325
package/assets/web/index.html
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
<meta name="theme-color" content="#0D1117" />
|
|
8
8
|
<title>Lightcode</title>
|
|
9
9
|
|
|
10
|
-
<link rel="stylesheet" crossorigin href="./index-
|
|
10
|
+
<link rel="stylesheet" crossorigin href="./index-2dshmdgm.css"><script type="module" crossorigin src="./index-x9zna9dg.js"></script></head>
|
|
11
11
|
<body>
|
|
12
12
|
<!--
|
|
13
13
|
THESIS: One local coding workspace shared with the CLI; this surface rejects dashboard-card clutter and keeps the project conversation central.
|
|
14
14
|
OWN-WORLD: Charcoal #0D1117/#121820/#18212B, hairline #273241, amber #F2A65A, workhorse system sans, and ui-monospace only for code and data.
|
|
15
|
-
STORY: Choose a
|
|
15
|
+
STORY: Choose a project from a familiar local folder, create or resume a session, stream the work, and resolve approvals without leaving the conversation.
|
|
16
16
|
FIRST VIEWPORT: A 264px session rail, an amber workspace ribbon, and one centered conversation/composer; below 760px the rail becomes a drawer.
|
|
17
17
|
FORM: Workspace-ribbon wayfinding, grounded candidate five and the assigned Operate structure; seed key bf554f40.
|
|
18
18
|
FINISH: unreviewed and undocumented is unfinished; this build ends with the finish review, the verdict, and DESIGN.md
|
package/cli.js
CHANGED
|
@@ -124,7 +124,7 @@ var init_logger = __esm(() => {
|
|
|
124
124
|
});
|
|
125
125
|
|
|
126
126
|
// packages/shared/src/version.ts
|
|
127
|
-
var lightcodeVersion = "0.
|
|
127
|
+
var lightcodeVersion = "0.14.0";
|
|
128
128
|
|
|
129
129
|
// packages/shared/src/index.ts
|
|
130
130
|
var productName = "Lightcode";
|
|
@@ -10093,7 +10093,13 @@ var workspaceLocationIdSchema, workspaceLocationStateSchema, workspaceLocationSc
|
|
|
10093
10093
|
var init_workspace_browser = __esm(() => {
|
|
10094
10094
|
init_coding_agent_modes();
|
|
10095
10095
|
init_permissions();
|
|
10096
|
-
workspaceLocationIdSchema = z37.
|
|
10096
|
+
workspaceLocationIdSchema = z37.enum([
|
|
10097
|
+
"desktop",
|
|
10098
|
+
"home",
|
|
10099
|
+
"documents",
|
|
10100
|
+
"downloads",
|
|
10101
|
+
"projects"
|
|
10102
|
+
]);
|
|
10097
10103
|
workspaceLocationStateSchema = z37.enum([
|
|
10098
10104
|
"unprobed",
|
|
10099
10105
|
"available"
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -515,6 +515,9 @@ function loadWebAuthStateFromEnvironment(env = Bun.env) {
|
|
|
515
515
|
function isPublicWebPath(pathname) {
|
|
516
516
|
return pathname === "/healthz" || pathname === "/app" || pathname === "/app/" || pathname.startsWith("/app/");
|
|
517
517
|
}
|
|
518
|
+
function isPublicWebAppPath(pathname) {
|
|
519
|
+
return pathname === "/app" || pathname === "/app/" || pathname.startsWith("/app/");
|
|
520
|
+
}
|
|
518
521
|
function looksLikeBrowserRequest(request) {
|
|
519
522
|
const headers = request.headers;
|
|
520
523
|
if (headers.get("x-lightcode-client")?.toLowerCase() === "web") {
|
|
@@ -577,8 +580,9 @@ function createWebSecurityMiddleware(options = {}) {
|
|
|
577
580
|
return jsonSecurityError(c, 403, "web_origin_forbidden", "The browser origin is not allowed.");
|
|
578
581
|
}
|
|
579
582
|
const fetchSite = request.headers.get("sec-fetch-site");
|
|
583
|
+
const isBootstrapDocumentNavigation = isPublicWebAppPath(pathname) && (request.method === "GET" || request.method === "HEAD") && request.headers.get("sec-fetch-mode") === "navigate" && request.headers.get("sec-fetch-dest") === "document";
|
|
580
584
|
const allowedFetchSites = publicPath ? new Set(["same-origin", "none"]) : new Set(["same-origin"]);
|
|
581
|
-
if (browserRequest && fetchSite !== null && !allowedFetchSites.has(fetchSite)) {
|
|
585
|
+
if (browserRequest && fetchSite !== null && !allowedFetchSites.has(fetchSite) && !(fetchSite === "cross-site" && isBootstrapDocumentNavigation)) {
|
|
582
586
|
return jsonSecurityError(c, 403, "web_fetch_forbidden", "Cross-site browser requests are not allowed.");
|
|
583
587
|
}
|
|
584
588
|
if (!publicPath && browserRequest) {
|
|
@@ -9522,7 +9526,13 @@ var workspaceLocationIdSchema, workspaceLocationStateSchema, workspaceLocationSc
|
|
|
9522
9526
|
var init_workspace_browser = __esm(() => {
|
|
9523
9527
|
init_coding_agent_modes();
|
|
9524
9528
|
init_permissions();
|
|
9525
|
-
workspaceLocationIdSchema = z38.
|
|
9529
|
+
workspaceLocationIdSchema = z38.enum([
|
|
9530
|
+
"desktop",
|
|
9531
|
+
"home",
|
|
9532
|
+
"documents",
|
|
9533
|
+
"downloads",
|
|
9534
|
+
"projects"
|
|
9535
|
+
]);
|
|
9526
9536
|
workspaceLocationStateSchema = z38.enum([
|
|
9527
9537
|
"unprobed",
|
|
9528
9538
|
"available"
|
|
@@ -16804,7 +16814,7 @@ import { Hono as Hono7 } from "hono";
|
|
|
16804
16814
|
function pathImplementation(platform4) {
|
|
16805
16815
|
return platform4 === "win32" ? path24.win32 : path24.posix;
|
|
16806
16816
|
}
|
|
16807
|
-
function
|
|
16817
|
+
function normalizeKnownFolderCandidate(candidate, platform4) {
|
|
16808
16818
|
if (candidate.includes("\x00")) {
|
|
16809
16819
|
return null;
|
|
16810
16820
|
}
|
|
@@ -16848,22 +16858,23 @@ async function readSmallUserDirsConfiguration(filePath) {
|
|
|
16848
16858
|
});
|
|
16849
16859
|
}
|
|
16850
16860
|
}
|
|
16851
|
-
async function
|
|
16861
|
+
async function resolveWindowsKnownFolder(environment, folder) {
|
|
16852
16862
|
try {
|
|
16853
16863
|
const systemRootCandidate = environment.SystemRoot?.trim() || environment.WINDIR?.trim() || "C:\\Windows";
|
|
16854
|
-
const windowsRoot =
|
|
16864
|
+
const windowsRoot = normalizeKnownFolderCandidate(systemRootCandidate, "win32");
|
|
16855
16865
|
if (!windowsRoot) {
|
|
16856
16866
|
return null;
|
|
16857
16867
|
}
|
|
16858
16868
|
const systemDirectory = path24.win32.join(windowsRoot, "System32");
|
|
16859
16869
|
const executable = path24.win32.join(systemDirectory, "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
16870
|
+
const folderExpression = folder === "desktop" ? "[Environment]::GetFolderPath([Environment+SpecialFolder]::DesktopDirectory)" : folder === "documents" ? "[Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments)" : "[Environment]::ExpandEnvironmentVariables([string](Get-ItemProperty -LiteralPath 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders' -Name '{374DE290-123F-4565-9164-39C4925E467B}' -ErrorAction Stop).'{374DE290-123F-4565-9164-39C4925E467B}')";
|
|
16860
16871
|
const child = Bun.spawn([
|
|
16861
16872
|
executable,
|
|
16862
16873
|
"-NoLogo",
|
|
16863
16874
|
"-NoProfile",
|
|
16864
16875
|
"-NonInteractive",
|
|
16865
16876
|
"-Command",
|
|
16866
|
-
|
|
16877
|
+
`[Console]::OutputEncoding=[Text.UTF8Encoding]::new($false);[Console]::Write(${folderExpression})`
|
|
16867
16878
|
], {
|
|
16868
16879
|
cwd: systemDirectory,
|
|
16869
16880
|
env: {
|
|
@@ -16900,13 +16911,13 @@ async function resolveWindowsDesktopKnownFolder(environment) {
|
|
|
16900
16911
|
return null;
|
|
16901
16912
|
}
|
|
16902
16913
|
}
|
|
16903
|
-
function
|
|
16914
|
+
function parseXdgUserDirectory(contents, homeDirectory, variableName) {
|
|
16904
16915
|
for (const line of contents.split(/\r?\n/)) {
|
|
16905
|
-
const match = line.match(/^\s*
|
|
16906
|
-
if (!match) {
|
|
16916
|
+
const match = line.match(/^\s*(XDG_(?:DESKTOP|DOCUMENTS|DOWNLOAD)_DIR)="([^"\r\n]*)"\s*$/);
|
|
16917
|
+
if (!match || match[1] !== variableName) {
|
|
16907
16918
|
continue;
|
|
16908
16919
|
}
|
|
16909
|
-
const configured = match[
|
|
16920
|
+
const configured = match[2];
|
|
16910
16921
|
if (!configured || configured.includes("\x00") || /[`]/.test(configured)) {
|
|
16911
16922
|
return null;
|
|
16912
16923
|
}
|
|
@@ -16926,6 +16937,28 @@ function parseXdgDesktopDirectory(contents, homeDirectory) {
|
|
|
16926
16937
|
}
|
|
16927
16938
|
return null;
|
|
16928
16939
|
}
|
|
16940
|
+
async function firstUsableKnownFolder(candidates, platform4, probe) {
|
|
16941
|
+
const seen = new Set;
|
|
16942
|
+
for (const unvalidated of candidates) {
|
|
16943
|
+
if (!unvalidated) {
|
|
16944
|
+
continue;
|
|
16945
|
+
}
|
|
16946
|
+
const candidate = normalizeKnownFolderCandidate(unvalidated, platform4);
|
|
16947
|
+
if (!candidate) {
|
|
16948
|
+
continue;
|
|
16949
|
+
}
|
|
16950
|
+
const key = platform4 === "win32" ? candidate.toLowerCase() : candidate;
|
|
16951
|
+
if (seen.has(key)) {
|
|
16952
|
+
continue;
|
|
16953
|
+
}
|
|
16954
|
+
seen.add(key);
|
|
16955
|
+
const state = await probe(candidate);
|
|
16956
|
+
if (state === "available" || state === "permission-denied") {
|
|
16957
|
+
return candidate;
|
|
16958
|
+
}
|
|
16959
|
+
}
|
|
16960
|
+
return null;
|
|
16961
|
+
}
|
|
16929
16962
|
async function resolveDesktopDirectoryPath(options = {}) {
|
|
16930
16963
|
const platform4 = options.platform ?? process.platform;
|
|
16931
16964
|
const pathApi = pathImplementation(platform4);
|
|
@@ -16933,35 +16966,16 @@ async function resolveDesktopDirectoryPath(options = {}) {
|
|
|
16933
16966
|
const homeDirectory = pathApi.normalize(options.homeDirectory ?? homedir4());
|
|
16934
16967
|
const explicit = environment.LIGHTCODE_DESKTOP_PATH?.trim();
|
|
16935
16968
|
if (explicit) {
|
|
16936
|
-
const normalized =
|
|
16969
|
+
const normalized = normalizeKnownFolderCandidate(explicit, platform4);
|
|
16937
16970
|
if (!normalized) {
|
|
16938
16971
|
throw new WorkspaceRouteError("workspace_unavailable", "LIGHTCODE_DESKTOP_PATH must be an absolute folder path and cannot be a filesystem root.", 500, false);
|
|
16939
16972
|
}
|
|
16940
16973
|
return normalized;
|
|
16941
16974
|
}
|
|
16942
16975
|
const probe = options.probeDirectory ?? probeDirectory;
|
|
16943
|
-
const firstAvailable = async (candidates) => {
|
|
16944
|
-
const seen = new Set;
|
|
16945
|
-
for (const unvalidated of candidates) {
|
|
16946
|
-
const candidate = normalizeDesktopCandidate(unvalidated, platform4);
|
|
16947
|
-
if (!candidate) {
|
|
16948
|
-
continue;
|
|
16949
|
-
}
|
|
16950
|
-
const key = platform4 === "win32" ? candidate.toLowerCase() : candidate;
|
|
16951
|
-
if (seen.has(key)) {
|
|
16952
|
-
continue;
|
|
16953
|
-
}
|
|
16954
|
-
seen.add(key);
|
|
16955
|
-
const state = await probe(candidate);
|
|
16956
|
-
if (state === "available" || state === "permission-denied") {
|
|
16957
|
-
return candidate;
|
|
16958
|
-
}
|
|
16959
|
-
}
|
|
16960
|
-
return null;
|
|
16961
|
-
};
|
|
16962
16976
|
if (platform4 === "win32") {
|
|
16963
16977
|
const userProfile = environment.USERPROFILE?.trim() || homeDirectory;
|
|
16964
|
-
const knownFolder = await (options.resolveWindowsKnownFolder ?? (() =>
|
|
16978
|
+
const knownFolder = await (options.resolveWindowsKnownFolder ?? (() => resolveWindowsKnownFolder(environment, "desktop")))();
|
|
16965
16979
|
const candidates = [
|
|
16966
16980
|
knownFolder,
|
|
16967
16981
|
environment.OneDrive,
|
|
@@ -16977,16 +16991,16 @@ async function resolveDesktopDirectoryPath(options = {}) {
|
|
|
16977
16991
|
];
|
|
16978
16992
|
});
|
|
16979
16993
|
candidates.push(path24.win32.join(userProfile, "Desktop"));
|
|
16980
|
-
return await
|
|
16994
|
+
return await firstUsableKnownFolder(candidates, platform4, probe) ?? path24.win32.join(homeDirectory, "Desktop");
|
|
16981
16995
|
}
|
|
16982
16996
|
if (platform4 === "linux") {
|
|
16983
16997
|
const configHome = environment.XDG_CONFIG_HOME?.trim();
|
|
16984
16998
|
const safeConfigHome = configHome && path24.posix.isAbsolute(configHome) ? path24.posix.normalize(configHome) : path24.posix.join(homeDirectory, ".config");
|
|
16985
16999
|
const readConfiguration = options.readUserDirsConfiguration ?? readSmallUserDirsConfiguration;
|
|
16986
17000
|
const contents = await readConfiguration(path24.posix.join(safeConfigHome, "user-dirs.dirs"));
|
|
16987
|
-
const configured = contents ?
|
|
17001
|
+
const configured = contents ? parseXdgUserDirectory(contents, homeDirectory, "XDG_DESKTOP_DIR") : null;
|
|
16988
17002
|
if (configured) {
|
|
16989
|
-
const selected = await
|
|
17003
|
+
const selected = await firstUsableKnownFolder([configured], platform4, probe);
|
|
16990
17004
|
if (selected) {
|
|
16991
17005
|
return selected;
|
|
16992
17006
|
}
|
|
@@ -16994,6 +17008,89 @@ async function resolveDesktopDirectoryPath(options = {}) {
|
|
|
16994
17008
|
}
|
|
16995
17009
|
return pathApi.join(homeDirectory, "Desktop");
|
|
16996
17010
|
}
|
|
17011
|
+
async function resolveUserDirectoryPath(folder, options) {
|
|
17012
|
+
const platform4 = options.platform ?? process.platform;
|
|
17013
|
+
const pathApi = pathImplementation(platform4);
|
|
17014
|
+
const environment = options.environment ?? Bun.env;
|
|
17015
|
+
const homeDirectory = pathApi.normalize(options.homeDirectory ?? homedir4());
|
|
17016
|
+
const displayName = folder === "documents" ? "Documents" : "Downloads";
|
|
17017
|
+
const overrideName = folder === "documents" ? "LIGHTCODE_DOCUMENTS_PATH" : "LIGHTCODE_DOWNLOADS_PATH";
|
|
17018
|
+
const explicit = environment[overrideName]?.trim();
|
|
17019
|
+
if (explicit) {
|
|
17020
|
+
const normalized = normalizeKnownFolderCandidate(explicit, platform4);
|
|
17021
|
+
if (!normalized) {
|
|
17022
|
+
throw new WorkspaceRouteError("workspace_unavailable", `${overrideName} must be an absolute folder path and cannot be a filesystem root.`, 500, false);
|
|
17023
|
+
}
|
|
17024
|
+
return normalized;
|
|
17025
|
+
}
|
|
17026
|
+
const probe = options.probeDirectory ?? probeDirectory;
|
|
17027
|
+
if (platform4 === "win32") {
|
|
17028
|
+
const knownFolder = await (options.resolveWindowsKnownFolder ?? (() => resolveWindowsKnownFolder(environment, folder)))();
|
|
17029
|
+
const userProfile = environment.USERPROFILE?.trim() || homeDirectory;
|
|
17030
|
+
const cloudRoots = [
|
|
17031
|
+
environment.OneDrive,
|
|
17032
|
+
environment.OneDriveConsumer,
|
|
17033
|
+
environment.OneDriveCommercial
|
|
17034
|
+
];
|
|
17035
|
+
const candidates = [
|
|
17036
|
+
knownFolder,
|
|
17037
|
+
...cloudRoots.map((root) => root?.trim() ? path24.win32.join(root.trim(), displayName) : null),
|
|
17038
|
+
path24.win32.join(userProfile, displayName),
|
|
17039
|
+
path24.win32.join(homeDirectory, displayName)
|
|
17040
|
+
];
|
|
17041
|
+
return await firstUsableKnownFolder(candidates, platform4, probe) ?? path24.win32.join(homeDirectory, displayName);
|
|
17042
|
+
}
|
|
17043
|
+
if (platform4 === "linux") {
|
|
17044
|
+
const configHome = environment.XDG_CONFIG_HOME?.trim();
|
|
17045
|
+
const safeConfigHome = configHome && path24.posix.isAbsolute(configHome) ? path24.posix.normalize(configHome) : path24.posix.join(homeDirectory, ".config");
|
|
17046
|
+
const readConfiguration = options.readUserDirsConfiguration ?? readSmallUserDirsConfiguration;
|
|
17047
|
+
const contents = await readConfiguration(path24.posix.join(safeConfigHome, "user-dirs.dirs"));
|
|
17048
|
+
const configured = contents ? parseXdgUserDirectory(contents, homeDirectory, folder === "documents" ? "XDG_DOCUMENTS_DIR" : "XDG_DOWNLOAD_DIR") : null;
|
|
17049
|
+
const selected = await firstUsableKnownFolder([configured], platform4, probe);
|
|
17050
|
+
if (selected) {
|
|
17051
|
+
return selected;
|
|
17052
|
+
}
|
|
17053
|
+
}
|
|
17054
|
+
return pathApi.join(homeDirectory, displayName);
|
|
17055
|
+
}
|
|
17056
|
+
function resolveDocumentsDirectoryPath(options = {}) {
|
|
17057
|
+
return resolveUserDirectoryPath("documents", options);
|
|
17058
|
+
}
|
|
17059
|
+
function resolveDownloadsDirectoryPath(options = {}) {
|
|
17060
|
+
return resolveUserDirectoryPath("downloads", options);
|
|
17061
|
+
}
|
|
17062
|
+
async function resolveProjectsDirectoryPath(options = {}) {
|
|
17063
|
+
const platform4 = options.platform ?? process.platform;
|
|
17064
|
+
const pathApi = pathImplementation(platform4);
|
|
17065
|
+
const environment = options.environment ?? Bun.env;
|
|
17066
|
+
const homeDirectory = pathApi.normalize(options.homeDirectory ?? homedir4());
|
|
17067
|
+
const explicit = environment.LIGHTCODE_PROJECTS_PATH?.trim();
|
|
17068
|
+
if (explicit) {
|
|
17069
|
+
const normalized = normalizeKnownFolderCandidate(explicit, platform4);
|
|
17070
|
+
if (!normalized) {
|
|
17071
|
+
throw new WorkspaceRouteError("workspace_unavailable", "LIGHTCODE_PROJECTS_PATH must be an absolute folder path and cannot be a filesystem root.", 500, false);
|
|
17072
|
+
}
|
|
17073
|
+
return normalized;
|
|
17074
|
+
}
|
|
17075
|
+
const probe = options.probeDirectory ?? probeDirectory;
|
|
17076
|
+
const candidates = [
|
|
17077
|
+
pathApi.join(homeDirectory, "Projects"),
|
|
17078
|
+
pathApi.join(homeDirectory, "projects")
|
|
17079
|
+
];
|
|
17080
|
+
const seen = new Set;
|
|
17081
|
+
for (const candidate of candidates) {
|
|
17082
|
+
const key = platform4 === "win32" ? candidate.toLowerCase() : candidate;
|
|
17083
|
+
if (seen.has(key)) {
|
|
17084
|
+
continue;
|
|
17085
|
+
}
|
|
17086
|
+
seen.add(key);
|
|
17087
|
+
const state = await probe(candidate);
|
|
17088
|
+
if (state === "available" || state === "permission-denied") {
|
|
17089
|
+
return candidate;
|
|
17090
|
+
}
|
|
17091
|
+
}
|
|
17092
|
+
return candidates[0];
|
|
17093
|
+
}
|
|
16997
17094
|
function filesystemErrorCode(error) {
|
|
16998
17095
|
if (typeof error !== "object" || error === null) {
|
|
16999
17096
|
return;
|
|
@@ -17053,6 +17150,9 @@ function isContainedPath2(root, candidate) {
|
|
|
17053
17150
|
async function resolveKnownDirectory(directory) {
|
|
17054
17151
|
try {
|
|
17055
17152
|
const root = await fs8.realpath(path24.resolve(directory));
|
|
17153
|
+
if (root === path24.parse(root).root) {
|
|
17154
|
+
throw new WorkspaceRouteError("workspace_unavailable", "A filesystem root cannot be opened as a workspace location.", 500, false);
|
|
17155
|
+
}
|
|
17056
17156
|
const stats = await fs8.stat(root, { bigint: true });
|
|
17057
17157
|
if (!stats.isDirectory()) {
|
|
17058
17158
|
throw new WorkspaceRouteError("workspace_not_directory", "The selected path is not a directory.", 422, false);
|
|
@@ -17082,11 +17182,11 @@ async function assertRootIdentity({
|
|
|
17082
17182
|
throw mapFilesystemError(error);
|
|
17083
17183
|
}
|
|
17084
17184
|
}
|
|
17085
|
-
async function symlinkFailure(root, candidate) {
|
|
17185
|
+
async function symlinkFailure(root, candidate, locationName) {
|
|
17086
17186
|
try {
|
|
17087
17187
|
const target = await fs8.realpath(candidate);
|
|
17088
17188
|
if (!isContainedPath2(root, target)) {
|
|
17089
|
-
return new WorkspaceRouteError("workspace_symlink_escape",
|
|
17189
|
+
return new WorkspaceRouteError("workspace_symlink_escape", `This symbolic link points outside the allowed ${locationName} location.`, 403, false);
|
|
17090
17190
|
}
|
|
17091
17191
|
return new WorkspaceRouteError("workspace_symlink_not_traversable", "Symbolic links cannot be used to navigate the workspace browser.", 422, false);
|
|
17092
17192
|
} catch (error) {
|
|
@@ -17096,12 +17196,12 @@ async function symlinkFailure(root, candidate) {
|
|
|
17096
17196
|
return new WorkspaceRouteError("workspace_symlink_broken", "This symbolic link is broken and cannot be opened.", 422, false);
|
|
17097
17197
|
}
|
|
17098
17198
|
}
|
|
17099
|
-
async function resolveDirectoryFromSegments(root, segments) {
|
|
17199
|
+
async function resolveDirectoryFromSegments(root, segments, locationName) {
|
|
17100
17200
|
let current = root;
|
|
17101
17201
|
for (const segment of segments) {
|
|
17102
17202
|
const candidate = path24.join(current, segment);
|
|
17103
17203
|
if (!isContainedPath2(root, candidate)) {
|
|
17104
|
-
throw new WorkspaceRouteError("workspace_symlink_escape",
|
|
17204
|
+
throw new WorkspaceRouteError("workspace_symlink_escape", `The requested path is outside the allowed ${locationName} location.`, 403, false);
|
|
17105
17205
|
}
|
|
17106
17206
|
let stats;
|
|
17107
17207
|
try {
|
|
@@ -17110,7 +17210,7 @@ async function resolveDirectoryFromSegments(root, segments) {
|
|
|
17110
17210
|
throw mapFilesystemError(error);
|
|
17111
17211
|
}
|
|
17112
17212
|
if (stats.isSymbolicLink()) {
|
|
17113
|
-
throw await symlinkFailure(root, candidate);
|
|
17213
|
+
throw await symlinkFailure(root, candidate, locationName);
|
|
17114
17214
|
}
|
|
17115
17215
|
if (!stats.isDirectory()) {
|
|
17116
17216
|
throw new WorkspaceRouteError("workspace_not_directory", "The selected path is not a directory.", 422, false);
|
|
@@ -17121,7 +17221,7 @@ async function resolveDirectoryFromSegments(root, segments) {
|
|
|
17121
17221
|
throw mapFilesystemError(error);
|
|
17122
17222
|
});
|
|
17123
17223
|
if (!isContainedPath2(root, canonical)) {
|
|
17124
|
-
throw new WorkspaceRouteError("workspace_symlink_escape",
|
|
17224
|
+
throw new WorkspaceRouteError("workspace_symlink_escape", `The requested path is outside the allowed ${locationName} location.`, 403, false);
|
|
17125
17225
|
}
|
|
17126
17226
|
const metadata = await fs8.lstat(canonical, { bigint: true });
|
|
17127
17227
|
if (metadata.isSymbolicLink() || !metadata.isDirectory()) {
|
|
@@ -17251,17 +17351,78 @@ function trimOldestEntries(map, maximum) {
|
|
|
17251
17351
|
map.delete(oldest);
|
|
17252
17352
|
}
|
|
17253
17353
|
}
|
|
17354
|
+
function configuredKnownLocationResolver(options, id, fallback) {
|
|
17355
|
+
const configuredResolver = options.resolveLocationPaths?.[id];
|
|
17356
|
+
if (configuredResolver) {
|
|
17357
|
+
return configuredResolver;
|
|
17358
|
+
}
|
|
17359
|
+
const configuredPath = options.locationPaths?.[id];
|
|
17360
|
+
if (configuredPath !== undefined) {
|
|
17361
|
+
return async () => configuredPath;
|
|
17362
|
+
}
|
|
17363
|
+
return fallback;
|
|
17364
|
+
}
|
|
17365
|
+
function knownLocation(definition, state) {
|
|
17366
|
+
return {
|
|
17367
|
+
id: definition.id,
|
|
17368
|
+
name: definition.name,
|
|
17369
|
+
kind: "known-folder",
|
|
17370
|
+
state,
|
|
17371
|
+
pathLabel: definition.pathLabel
|
|
17372
|
+
};
|
|
17373
|
+
}
|
|
17254
17374
|
function createWorkspaceRoutes(options = {}) {
|
|
17255
17375
|
const now = options.now ?? Date.now;
|
|
17256
17376
|
const capabilityTtlMs = options.browserCapabilityTtlMs ?? defaultBrowserCapabilityTtlMs;
|
|
17257
|
-
const
|
|
17258
|
-
const normalized =
|
|
17377
|
+
const getLegacyDesktopPath = options.resolveDesktopPath ?? (options.desktopPath ? async () => {
|
|
17378
|
+
const normalized = normalizeKnownFolderCandidate(options.desktopPath, process.platform);
|
|
17259
17379
|
if (!normalized) {
|
|
17260
17380
|
throw new WorkspaceRouteError("workspace_unavailable", "The configured Desktop path must be absolute and cannot be a filesystem root.", 500, false);
|
|
17261
17381
|
}
|
|
17262
17382
|
return normalized;
|
|
17263
17383
|
} : () => resolveDesktopDirectoryPath());
|
|
17264
17384
|
const desktopPathLabel = options.desktopPathLabel ?? "Desktop";
|
|
17385
|
+
const defaultHomePath = async () => homedir4();
|
|
17386
|
+
const defaultDocumentsPath = () => resolveDocumentsDirectoryPath();
|
|
17387
|
+
const defaultDownloadsPath = () => resolveDownloadsDirectoryPath();
|
|
17388
|
+
const definitions = [
|
|
17389
|
+
{
|
|
17390
|
+
id: "desktop",
|
|
17391
|
+
name: "Desktop",
|
|
17392
|
+
rootName: "Desktop",
|
|
17393
|
+
pathLabel: options.locationPathLabels?.desktop ?? desktopPathLabel,
|
|
17394
|
+
resolvePath: configuredKnownLocationResolver(options, "desktop", getLegacyDesktopPath)
|
|
17395
|
+
},
|
|
17396
|
+
{
|
|
17397
|
+
id: "home",
|
|
17398
|
+
name: "Home (broad access)",
|
|
17399
|
+
rootName: "Home",
|
|
17400
|
+
pathLabel: options.locationPathLabels?.home ?? "Home",
|
|
17401
|
+
resolvePath: configuredKnownLocationResolver(options, "home", defaultHomePath)
|
|
17402
|
+
},
|
|
17403
|
+
{
|
|
17404
|
+
id: "documents",
|
|
17405
|
+
name: "Documents",
|
|
17406
|
+
rootName: "Documents",
|
|
17407
|
+
pathLabel: options.locationPathLabels?.documents ?? "Documents",
|
|
17408
|
+
resolvePath: configuredKnownLocationResolver(options, "documents", defaultDocumentsPath)
|
|
17409
|
+
},
|
|
17410
|
+
{
|
|
17411
|
+
id: "downloads",
|
|
17412
|
+
name: "Downloads",
|
|
17413
|
+
rootName: "Downloads",
|
|
17414
|
+
pathLabel: options.locationPathLabels?.downloads ?? "Downloads",
|
|
17415
|
+
resolvePath: configuredKnownLocationResolver(options, "downloads", defaultDownloadsPath)
|
|
17416
|
+
},
|
|
17417
|
+
{
|
|
17418
|
+
id: "projects",
|
|
17419
|
+
name: "Projects",
|
|
17420
|
+
rootName: "Projects",
|
|
17421
|
+
pathLabel: options.locationPathLabels?.projects ?? "Projects",
|
|
17422
|
+
resolvePath: configuredKnownLocationResolver(options, "projects", () => resolveProjectsDirectoryPath())
|
|
17423
|
+
}
|
|
17424
|
+
];
|
|
17425
|
+
const definitionsById = new Map(definitions.map((definition) => [definition.id, definition]));
|
|
17265
17426
|
const createSession = options.createSession ?? createChatSession;
|
|
17266
17427
|
const cursorSecret = randomBytes(32);
|
|
17267
17428
|
const browserCapabilities = new Map;
|
|
@@ -17270,7 +17431,7 @@ function createWorkspaceRoutes(options = {}) {
|
|
|
17270
17431
|
const capability2 = browserCapabilities.get(browserId);
|
|
17271
17432
|
if (!capability2 || capability2.expiresAtMs <= now()) {
|
|
17272
17433
|
browserCapabilities.delete(browserId);
|
|
17273
|
-
throw new WorkspaceRouteError("browser_capability_expired", "This folder browser expired. Open
|
|
17434
|
+
throw new WorkspaceRouteError("browser_capability_expired", capability2 ? `This folder browser expired. Open ${capability2.rootName} again.` : "This folder browser expired. Open a location again.", 410, true);
|
|
17274
17435
|
}
|
|
17275
17436
|
return capability2;
|
|
17276
17437
|
};
|
|
@@ -17282,15 +17443,7 @@ function createWorkspaceRoutes(options = {}) {
|
|
|
17282
17443
|
return grant;
|
|
17283
17444
|
};
|
|
17284
17445
|
return new Hono7().get("/locations", (c) => c.json(workspaceLocationsResponseSchema.parse({
|
|
17285
|
-
locations:
|
|
17286
|
-
{
|
|
17287
|
-
id: "desktop",
|
|
17288
|
-
name: "Desktop",
|
|
17289
|
-
kind: "known-folder",
|
|
17290
|
-
state: "unprobed",
|
|
17291
|
-
pathLabel: desktopPathLabel
|
|
17292
|
-
}
|
|
17293
|
-
]
|
|
17446
|
+
locations: definitions.map((definition) => knownLocation(definition, "unprobed"))
|
|
17294
17447
|
}))).post("/browser/open", zValidator3("json", workspaceBrowserOpenRequestSchema, (result, c) => {
|
|
17295
17448
|
if (!result.success) {
|
|
17296
17449
|
return invalidRequestResponse(c);
|
|
@@ -17298,32 +17451,30 @@ function createWorkspaceRoutes(options = {}) {
|
|
|
17298
17451
|
}), async (c) => {
|
|
17299
17452
|
try {
|
|
17300
17453
|
const { locationId } = c.req.valid("json");
|
|
17301
|
-
|
|
17454
|
+
const definition = definitionsById.get(locationId);
|
|
17455
|
+
if (!definition) {
|
|
17302
17456
|
throw new WorkspaceRouteError("location_not_found", "Workspace location not found.", 404, false);
|
|
17303
17457
|
}
|
|
17304
|
-
const
|
|
17305
|
-
const
|
|
17306
|
-
if (!
|
|
17307
|
-
throw new WorkspaceRouteError("workspace_unavailable",
|
|
17458
|
+
const discoveredPath = await definition.resolvePath();
|
|
17459
|
+
const safePath = normalizeKnownFolderCandidate(discoveredPath, process.platform);
|
|
17460
|
+
if (!safePath) {
|
|
17461
|
+
throw new WorkspaceRouteError("workspace_unavailable", `The resolved ${definition.rootName} location is not a safe absolute folder path.`, 500, false);
|
|
17308
17462
|
}
|
|
17309
|
-
const resolved = await resolveKnownDirectory(
|
|
17463
|
+
const resolved = await resolveKnownDirectory(safePath);
|
|
17310
17464
|
const browserId = crypto.randomUUID();
|
|
17311
17465
|
const expiresAtMs = now() + capabilityTtlMs;
|
|
17466
|
+
const location = knownLocation(definition, "available");
|
|
17312
17467
|
browserCapabilities.set(browserId, {
|
|
17313
17468
|
id: browserId,
|
|
17314
17469
|
...resolved,
|
|
17470
|
+
location,
|
|
17471
|
+
rootName: definition.rootName,
|
|
17315
17472
|
expiresAtMs
|
|
17316
17473
|
});
|
|
17317
17474
|
trimOldestEntries(browserCapabilities, maximumBrowserCapabilities);
|
|
17318
17475
|
return c.json(workspaceBrowserOpenResponseSchema.parse({
|
|
17319
17476
|
browserId,
|
|
17320
|
-
location
|
|
17321
|
-
id: "desktop",
|
|
17322
|
-
name: "Desktop",
|
|
17323
|
-
kind: "known-folder",
|
|
17324
|
-
state: "available",
|
|
17325
|
-
pathLabel: desktopPathLabel
|
|
17326
|
-
},
|
|
17477
|
+
location,
|
|
17327
17478
|
expiresAt: new Date(expiresAtMs).toISOString()
|
|
17328
17479
|
}), 201);
|
|
17329
17480
|
} catch (error) {
|
|
@@ -17343,7 +17494,7 @@ function createWorkspaceRoutes(options = {}) {
|
|
|
17343
17494
|
const input = c.req.valid("json");
|
|
17344
17495
|
const capability2 = getBrowserCapability(browserId);
|
|
17345
17496
|
await assertRootIdentity(capability2);
|
|
17346
|
-
const directory = await resolveDirectoryFromSegments(capability2.root, input.segments);
|
|
17497
|
+
const directory = await resolveDirectoryFromSegments(capability2.root, input.segments, capability2.rootName);
|
|
17347
17498
|
const beforeRead = await assertDirectoryIdentity(capability2.root, directory);
|
|
17348
17499
|
const cursorBinding = {
|
|
17349
17500
|
browserId,
|
|
@@ -17421,7 +17572,7 @@ function createWorkspaceRoutes(options = {}) {
|
|
|
17421
17572
|
const { segments } = c.req.valid("json");
|
|
17422
17573
|
const capability2 = getBrowserCapability(browserId);
|
|
17423
17574
|
await assertRootIdentity(capability2);
|
|
17424
|
-
const directory = await resolveDirectoryFromSegments(capability2.root, segments);
|
|
17575
|
+
const directory = await resolveDirectoryFromSegments(capability2.root, segments, capability2.rootName);
|
|
17425
17576
|
await assertDirectoryIdentity(capability2.root, directory);
|
|
17426
17577
|
await options.beforeDirectoryOperation?.({
|
|
17427
17578
|
operation: "select",
|
|
@@ -17430,8 +17581,11 @@ function createWorkspaceRoutes(options = {}) {
|
|
|
17430
17581
|
await fs8.access(directory.path, fsConstants2.R_OK | fsConstants2.X_OK);
|
|
17431
17582
|
const createdAt = new Date(now()).toISOString();
|
|
17432
17583
|
const id = crypto.randomUUID();
|
|
17433
|
-
const name = segments.at(-1) ??
|
|
17434
|
-
const pathLabel = [
|
|
17584
|
+
const name = segments.at(-1) ?? capability2.rootName;
|
|
17585
|
+
const pathLabel = [
|
|
17586
|
+
capability2.location.pathLabel,
|
|
17587
|
+
...segments
|
|
17588
|
+
].join("/");
|
|
17435
17589
|
const grant = {
|
|
17436
17590
|
id,
|
|
17437
17591
|
name,
|