@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.
@@ -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-21aq5q6q.css"><script type="module" crossorigin src="./index-j5mzdh93.js"></script></head>
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 Desktop project, create or resume a session, stream the work, and resolve approvals without leaving the conversation.
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.13.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.literal("desktop");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kmugalkhod/lightcode",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Lightcode — a local AI coding agent with terminal and browser interfaces.",
5
5
  "type": "module",
6
6
  "license": "MIT",
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.literal("desktop");
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 normalizeDesktopCandidate(candidate, platform4) {
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 resolveWindowsDesktopKnownFolder(environment) {
16861
+ async function resolveWindowsKnownFolder(environment, folder) {
16852
16862
  try {
16853
16863
  const systemRootCandidate = environment.SystemRoot?.trim() || environment.WINDIR?.trim() || "C:\\Windows";
16854
- const windowsRoot = normalizeDesktopCandidate(systemRootCandidate, "win32");
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
- "[Console]::OutputEncoding=[Text.UTF8Encoding]::new($false);[Console]::Write([Environment]::GetFolderPath([Environment+SpecialFolder]::DesktopDirectory))"
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 parseXdgDesktopDirectory(contents, homeDirectory) {
16914
+ function parseXdgUserDirectory(contents, homeDirectory, variableName) {
16904
16915
  for (const line of contents.split(/\r?\n/)) {
16905
- const match = line.match(/^\s*XDG_DESKTOP_DIR="([^"\r\n]*)"\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[1];
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 = normalizeDesktopCandidate(explicit, platform4);
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 ?? (() => resolveWindowsDesktopKnownFolder(environment)))();
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 firstAvailable(candidates) ?? path24.win32.join(homeDirectory, "Desktop");
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 ? parseXdgDesktopDirectory(contents, homeDirectory) : null;
17001
+ const configured = contents ? parseXdgUserDirectory(contents, homeDirectory, "XDG_DESKTOP_DIR") : null;
16988
17002
  if (configured) {
16989
- const selected = await firstAvailable([configured]);
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", "This symbolic link points outside the allowed Desktop location.", 403, false);
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", "The requested path is outside the allowed Desktop location.", 403, false);
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", "The requested path is outside the allowed Desktop location.", 403, false);
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 getDesktopPath = options.resolveDesktopPath ?? (options.desktopPath ? async () => {
17258
- const normalized = normalizeDesktopCandidate(options.desktopPath, process.platform);
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 Desktop again.", 410, true);
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
- if (locationId !== "desktop") {
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 discoveredDesktop = await getDesktopPath();
17305
- const safeDesktop = normalizeDesktopCandidate(discoveredDesktop, process.platform);
17306
- if (!safeDesktop) {
17307
- throw new WorkspaceRouteError("workspace_unavailable", "The resolved Desktop location is not a safe absolute folder path.", 500, false);
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(safeDesktop);
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) ?? "Desktop";
17434
- const pathLabel = [desktopPathLabel, ...segments].join("/");
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,