@kmugalkhod/lightcode 0.13.1 → 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.1";
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.1",
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
@@ -9526,7 +9526,13 @@ var workspaceLocationIdSchema, workspaceLocationStateSchema, workspaceLocationSc
9526
9526
  var init_workspace_browser = __esm(() => {
9527
9527
  init_coding_agent_modes();
9528
9528
  init_permissions();
9529
- workspaceLocationIdSchema = z38.literal("desktop");
9529
+ workspaceLocationIdSchema = z38.enum([
9530
+ "desktop",
9531
+ "home",
9532
+ "documents",
9533
+ "downloads",
9534
+ "projects"
9535
+ ]);
9530
9536
  workspaceLocationStateSchema = z38.enum([
9531
9537
  "unprobed",
9532
9538
  "available"
@@ -16808,7 +16814,7 @@ import { Hono as Hono7 } from "hono";
16808
16814
  function pathImplementation(platform4) {
16809
16815
  return platform4 === "win32" ? path24.win32 : path24.posix;
16810
16816
  }
16811
- function normalizeDesktopCandidate(candidate, platform4) {
16817
+ function normalizeKnownFolderCandidate(candidate, platform4) {
16812
16818
  if (candidate.includes("\x00")) {
16813
16819
  return null;
16814
16820
  }
@@ -16852,22 +16858,23 @@ async function readSmallUserDirsConfiguration(filePath) {
16852
16858
  });
16853
16859
  }
16854
16860
  }
16855
- async function resolveWindowsDesktopKnownFolder(environment) {
16861
+ async function resolveWindowsKnownFolder(environment, folder) {
16856
16862
  try {
16857
16863
  const systemRootCandidate = environment.SystemRoot?.trim() || environment.WINDIR?.trim() || "C:\\Windows";
16858
- const windowsRoot = normalizeDesktopCandidate(systemRootCandidate, "win32");
16864
+ const windowsRoot = normalizeKnownFolderCandidate(systemRootCandidate, "win32");
16859
16865
  if (!windowsRoot) {
16860
16866
  return null;
16861
16867
  }
16862
16868
  const systemDirectory = path24.win32.join(windowsRoot, "System32");
16863
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}')";
16864
16871
  const child = Bun.spawn([
16865
16872
  executable,
16866
16873
  "-NoLogo",
16867
16874
  "-NoProfile",
16868
16875
  "-NonInteractive",
16869
16876
  "-Command",
16870
- "[Console]::OutputEncoding=[Text.UTF8Encoding]::new($false);[Console]::Write([Environment]::GetFolderPath([Environment+SpecialFolder]::DesktopDirectory))"
16877
+ `[Console]::OutputEncoding=[Text.UTF8Encoding]::new($false);[Console]::Write(${folderExpression})`
16871
16878
  ], {
16872
16879
  cwd: systemDirectory,
16873
16880
  env: {
@@ -16904,13 +16911,13 @@ async function resolveWindowsDesktopKnownFolder(environment) {
16904
16911
  return null;
16905
16912
  }
16906
16913
  }
16907
- function parseXdgDesktopDirectory(contents, homeDirectory) {
16914
+ function parseXdgUserDirectory(contents, homeDirectory, variableName) {
16908
16915
  for (const line of contents.split(/\r?\n/)) {
16909
- const match = line.match(/^\s*XDG_DESKTOP_DIR="([^"\r\n]*)"\s*$/);
16910
- if (!match) {
16916
+ const match = line.match(/^\s*(XDG_(?:DESKTOP|DOCUMENTS|DOWNLOAD)_DIR)="([^"\r\n]*)"\s*$/);
16917
+ if (!match || match[1] !== variableName) {
16911
16918
  continue;
16912
16919
  }
16913
- const configured = match[1];
16920
+ const configured = match[2];
16914
16921
  if (!configured || configured.includes("\x00") || /[`]/.test(configured)) {
16915
16922
  return null;
16916
16923
  }
@@ -16930,6 +16937,28 @@ function parseXdgDesktopDirectory(contents, homeDirectory) {
16930
16937
  }
16931
16938
  return null;
16932
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
+ }
16933
16962
  async function resolveDesktopDirectoryPath(options = {}) {
16934
16963
  const platform4 = options.platform ?? process.platform;
16935
16964
  const pathApi = pathImplementation(platform4);
@@ -16937,35 +16966,16 @@ async function resolveDesktopDirectoryPath(options = {}) {
16937
16966
  const homeDirectory = pathApi.normalize(options.homeDirectory ?? homedir4());
16938
16967
  const explicit = environment.LIGHTCODE_DESKTOP_PATH?.trim();
16939
16968
  if (explicit) {
16940
- const normalized = normalizeDesktopCandidate(explicit, platform4);
16969
+ const normalized = normalizeKnownFolderCandidate(explicit, platform4);
16941
16970
  if (!normalized) {
16942
16971
  throw new WorkspaceRouteError("workspace_unavailable", "LIGHTCODE_DESKTOP_PATH must be an absolute folder path and cannot be a filesystem root.", 500, false);
16943
16972
  }
16944
16973
  return normalized;
16945
16974
  }
16946
16975
  const probe = options.probeDirectory ?? probeDirectory;
16947
- const firstAvailable = async (candidates) => {
16948
- const seen = new Set;
16949
- for (const unvalidated of candidates) {
16950
- const candidate = normalizeDesktopCandidate(unvalidated, platform4);
16951
- if (!candidate) {
16952
- continue;
16953
- }
16954
- const key = platform4 === "win32" ? candidate.toLowerCase() : candidate;
16955
- if (seen.has(key)) {
16956
- continue;
16957
- }
16958
- seen.add(key);
16959
- const state = await probe(candidate);
16960
- if (state === "available" || state === "permission-denied") {
16961
- return candidate;
16962
- }
16963
- }
16964
- return null;
16965
- };
16966
16976
  if (platform4 === "win32") {
16967
16977
  const userProfile = environment.USERPROFILE?.trim() || homeDirectory;
16968
- const knownFolder = await (options.resolveWindowsKnownFolder ?? (() => resolveWindowsDesktopKnownFolder(environment)))();
16978
+ const knownFolder = await (options.resolveWindowsKnownFolder ?? (() => resolveWindowsKnownFolder(environment, "desktop")))();
16969
16979
  const candidates = [
16970
16980
  knownFolder,
16971
16981
  environment.OneDrive,
@@ -16981,16 +16991,16 @@ async function resolveDesktopDirectoryPath(options = {}) {
16981
16991
  ];
16982
16992
  });
16983
16993
  candidates.push(path24.win32.join(userProfile, "Desktop"));
16984
- return await firstAvailable(candidates) ?? path24.win32.join(homeDirectory, "Desktop");
16994
+ return await firstUsableKnownFolder(candidates, platform4, probe) ?? path24.win32.join(homeDirectory, "Desktop");
16985
16995
  }
16986
16996
  if (platform4 === "linux") {
16987
16997
  const configHome = environment.XDG_CONFIG_HOME?.trim();
16988
16998
  const safeConfigHome = configHome && path24.posix.isAbsolute(configHome) ? path24.posix.normalize(configHome) : path24.posix.join(homeDirectory, ".config");
16989
16999
  const readConfiguration = options.readUserDirsConfiguration ?? readSmallUserDirsConfiguration;
16990
17000
  const contents = await readConfiguration(path24.posix.join(safeConfigHome, "user-dirs.dirs"));
16991
- const configured = contents ? parseXdgDesktopDirectory(contents, homeDirectory) : null;
17001
+ const configured = contents ? parseXdgUserDirectory(contents, homeDirectory, "XDG_DESKTOP_DIR") : null;
16992
17002
  if (configured) {
16993
- const selected = await firstAvailable([configured]);
17003
+ const selected = await firstUsableKnownFolder([configured], platform4, probe);
16994
17004
  if (selected) {
16995
17005
  return selected;
16996
17006
  }
@@ -16998,6 +17008,89 @@ async function resolveDesktopDirectoryPath(options = {}) {
16998
17008
  }
16999
17009
  return pathApi.join(homeDirectory, "Desktop");
17000
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
+ }
17001
17094
  function filesystemErrorCode(error) {
17002
17095
  if (typeof error !== "object" || error === null) {
17003
17096
  return;
@@ -17057,6 +17150,9 @@ function isContainedPath2(root, candidate) {
17057
17150
  async function resolveKnownDirectory(directory) {
17058
17151
  try {
17059
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
+ }
17060
17156
  const stats = await fs8.stat(root, { bigint: true });
17061
17157
  if (!stats.isDirectory()) {
17062
17158
  throw new WorkspaceRouteError("workspace_not_directory", "The selected path is not a directory.", 422, false);
@@ -17086,11 +17182,11 @@ async function assertRootIdentity({
17086
17182
  throw mapFilesystemError(error);
17087
17183
  }
17088
17184
  }
17089
- async function symlinkFailure(root, candidate) {
17185
+ async function symlinkFailure(root, candidate, locationName) {
17090
17186
  try {
17091
17187
  const target = await fs8.realpath(candidate);
17092
17188
  if (!isContainedPath2(root, target)) {
17093
- 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);
17094
17190
  }
17095
17191
  return new WorkspaceRouteError("workspace_symlink_not_traversable", "Symbolic links cannot be used to navigate the workspace browser.", 422, false);
17096
17192
  } catch (error) {
@@ -17100,12 +17196,12 @@ async function symlinkFailure(root, candidate) {
17100
17196
  return new WorkspaceRouteError("workspace_symlink_broken", "This symbolic link is broken and cannot be opened.", 422, false);
17101
17197
  }
17102
17198
  }
17103
- async function resolveDirectoryFromSegments(root, segments) {
17199
+ async function resolveDirectoryFromSegments(root, segments, locationName) {
17104
17200
  let current = root;
17105
17201
  for (const segment of segments) {
17106
17202
  const candidate = path24.join(current, segment);
17107
17203
  if (!isContainedPath2(root, candidate)) {
17108
- 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);
17109
17205
  }
17110
17206
  let stats;
17111
17207
  try {
@@ -17114,7 +17210,7 @@ async function resolveDirectoryFromSegments(root, segments) {
17114
17210
  throw mapFilesystemError(error);
17115
17211
  }
17116
17212
  if (stats.isSymbolicLink()) {
17117
- throw await symlinkFailure(root, candidate);
17213
+ throw await symlinkFailure(root, candidate, locationName);
17118
17214
  }
17119
17215
  if (!stats.isDirectory()) {
17120
17216
  throw new WorkspaceRouteError("workspace_not_directory", "The selected path is not a directory.", 422, false);
@@ -17125,7 +17221,7 @@ async function resolveDirectoryFromSegments(root, segments) {
17125
17221
  throw mapFilesystemError(error);
17126
17222
  });
17127
17223
  if (!isContainedPath2(root, canonical)) {
17128
- 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);
17129
17225
  }
17130
17226
  const metadata = await fs8.lstat(canonical, { bigint: true });
17131
17227
  if (metadata.isSymbolicLink() || !metadata.isDirectory()) {
@@ -17255,17 +17351,78 @@ function trimOldestEntries(map, maximum) {
17255
17351
  map.delete(oldest);
17256
17352
  }
17257
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
+ }
17258
17374
  function createWorkspaceRoutes(options = {}) {
17259
17375
  const now = options.now ?? Date.now;
17260
17376
  const capabilityTtlMs = options.browserCapabilityTtlMs ?? defaultBrowserCapabilityTtlMs;
17261
- const getDesktopPath = options.resolveDesktopPath ?? (options.desktopPath ? async () => {
17262
- const normalized = normalizeDesktopCandidate(options.desktopPath, process.platform);
17377
+ const getLegacyDesktopPath = options.resolveDesktopPath ?? (options.desktopPath ? async () => {
17378
+ const normalized = normalizeKnownFolderCandidate(options.desktopPath, process.platform);
17263
17379
  if (!normalized) {
17264
17380
  throw new WorkspaceRouteError("workspace_unavailable", "The configured Desktop path must be absolute and cannot be a filesystem root.", 500, false);
17265
17381
  }
17266
17382
  return normalized;
17267
17383
  } : () => resolveDesktopDirectoryPath());
17268
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]));
17269
17426
  const createSession = options.createSession ?? createChatSession;
17270
17427
  const cursorSecret = randomBytes(32);
17271
17428
  const browserCapabilities = new Map;
@@ -17274,7 +17431,7 @@ function createWorkspaceRoutes(options = {}) {
17274
17431
  const capability2 = browserCapabilities.get(browserId);
17275
17432
  if (!capability2 || capability2.expiresAtMs <= now()) {
17276
17433
  browserCapabilities.delete(browserId);
17277
- 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);
17278
17435
  }
17279
17436
  return capability2;
17280
17437
  };
@@ -17286,15 +17443,7 @@ function createWorkspaceRoutes(options = {}) {
17286
17443
  return grant;
17287
17444
  };
17288
17445
  return new Hono7().get("/locations", (c) => c.json(workspaceLocationsResponseSchema.parse({
17289
- locations: [
17290
- {
17291
- id: "desktop",
17292
- name: "Desktop",
17293
- kind: "known-folder",
17294
- state: "unprobed",
17295
- pathLabel: desktopPathLabel
17296
- }
17297
- ]
17446
+ locations: definitions.map((definition) => knownLocation(definition, "unprobed"))
17298
17447
  }))).post("/browser/open", zValidator3("json", workspaceBrowserOpenRequestSchema, (result, c) => {
17299
17448
  if (!result.success) {
17300
17449
  return invalidRequestResponse(c);
@@ -17302,32 +17451,30 @@ function createWorkspaceRoutes(options = {}) {
17302
17451
  }), async (c) => {
17303
17452
  try {
17304
17453
  const { locationId } = c.req.valid("json");
17305
- if (locationId !== "desktop") {
17454
+ const definition = definitionsById.get(locationId);
17455
+ if (!definition) {
17306
17456
  throw new WorkspaceRouteError("location_not_found", "Workspace location not found.", 404, false);
17307
17457
  }
17308
- const discoveredDesktop = await getDesktopPath();
17309
- const safeDesktop = normalizeDesktopCandidate(discoveredDesktop, process.platform);
17310
- if (!safeDesktop) {
17311
- 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);
17312
17462
  }
17313
- const resolved = await resolveKnownDirectory(safeDesktop);
17463
+ const resolved = await resolveKnownDirectory(safePath);
17314
17464
  const browserId = crypto.randomUUID();
17315
17465
  const expiresAtMs = now() + capabilityTtlMs;
17466
+ const location = knownLocation(definition, "available");
17316
17467
  browserCapabilities.set(browserId, {
17317
17468
  id: browserId,
17318
17469
  ...resolved,
17470
+ location,
17471
+ rootName: definition.rootName,
17319
17472
  expiresAtMs
17320
17473
  });
17321
17474
  trimOldestEntries(browserCapabilities, maximumBrowserCapabilities);
17322
17475
  return c.json(workspaceBrowserOpenResponseSchema.parse({
17323
17476
  browserId,
17324
- location: {
17325
- id: "desktop",
17326
- name: "Desktop",
17327
- kind: "known-folder",
17328
- state: "available",
17329
- pathLabel: desktopPathLabel
17330
- },
17477
+ location,
17331
17478
  expiresAt: new Date(expiresAtMs).toISOString()
17332
17479
  }), 201);
17333
17480
  } catch (error) {
@@ -17347,7 +17494,7 @@ function createWorkspaceRoutes(options = {}) {
17347
17494
  const input = c.req.valid("json");
17348
17495
  const capability2 = getBrowserCapability(browserId);
17349
17496
  await assertRootIdentity(capability2);
17350
- const directory = await resolveDirectoryFromSegments(capability2.root, input.segments);
17497
+ const directory = await resolveDirectoryFromSegments(capability2.root, input.segments, capability2.rootName);
17351
17498
  const beforeRead = await assertDirectoryIdentity(capability2.root, directory);
17352
17499
  const cursorBinding = {
17353
17500
  browserId,
@@ -17425,7 +17572,7 @@ function createWorkspaceRoutes(options = {}) {
17425
17572
  const { segments } = c.req.valid("json");
17426
17573
  const capability2 = getBrowserCapability(browserId);
17427
17574
  await assertRootIdentity(capability2);
17428
- const directory = await resolveDirectoryFromSegments(capability2.root, segments);
17575
+ const directory = await resolveDirectoryFromSegments(capability2.root, segments, capability2.rootName);
17429
17576
  await assertDirectoryIdentity(capability2.root, directory);
17430
17577
  await options.beforeDirectoryOperation?.({
17431
17578
  operation: "select",
@@ -17434,8 +17581,11 @@ function createWorkspaceRoutes(options = {}) {
17434
17581
  await fs8.access(directory.path, fsConstants2.R_OK | fsConstants2.X_OK);
17435
17582
  const createdAt = new Date(now()).toISOString();
17436
17583
  const id = crypto.randomUUID();
17437
- const name = segments.at(-1) ?? "Desktop";
17438
- const pathLabel = [desktopPathLabel, ...segments].join("/");
17584
+ const name = segments.at(-1) ?? capability2.rootName;
17585
+ const pathLabel = [
17586
+ capability2.location.pathLabel,
17587
+ ...segments
17588
+ ].join("/");
17439
17589
  const grant = {
17440
17590
  id,
17441
17591
  name,