@kmugalkhod/lightcode 0.14.0 → 0.16.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/server.js CHANGED
@@ -428,6 +428,9 @@ import {
428
428
  } from "fs";
429
429
  import path4 from "path";
430
430
  import { z } from "zod";
431
+ function isAuthenticatedWebRequest(request) {
432
+ return authenticatedWebRequests.has(request);
433
+ }
431
434
  function defaultExpectedOrigin(env = Bun.env) {
432
435
  const configuredPort = env.PORT?.trim() || "4983";
433
436
  return new URL(`http://127.0.0.1:${configuredPort}`).origin;
@@ -603,6 +606,9 @@ function createWebSecurityMiddleware(options = {}) {
603
606
  if (!hasValidBearerToken(request, authState.token)) {
604
607
  return jsonSecurityError(c, 401, "web_auth_required", "A valid Lightcode browser token is required.");
605
608
  }
609
+ if (browserRequest) {
610
+ authenticatedWebRequests.add(request);
611
+ }
606
612
  } else if (browserRequest || authState.configured) {
607
613
  return jsonSecurityError(c, 503, "web_auth_unavailable", "Browser access is unavailable. Start Lightcode with `lightcode web`.");
608
614
  } else if (!allowUnauthenticatedCli) {
@@ -615,7 +621,7 @@ function createWebSecurityMiddleware(options = {}) {
615
621
  await next();
616
622
  };
617
623
  }
618
- var webAuthFileEnvironmentVariable = "LIGHTCODE_WEB_AUTH_FILE", webAuthHandoffSchema, maximumHandoffBytes = 4096, maximumHandoffAgeMs, maximumHandoffClockSkewMs, defaultAuthState;
624
+ var webAuthFileEnvironmentVariable = "LIGHTCODE_WEB_AUTH_FILE", webAuthHandoffSchema, maximumHandoffBytes = 4096, maximumHandoffAgeMs, maximumHandoffClockSkewMs, authenticatedWebRequests, defaultAuthState;
619
625
  var init_web_auth = __esm(() => {
620
626
  webAuthHandoffSchema = z.object({
621
627
  version: z.literal(1),
@@ -624,6 +630,7 @@ var init_web_auth = __esm(() => {
624
630
  }).strict();
625
631
  maximumHandoffAgeMs = 5 * 60 * 1000;
626
632
  maximumHandoffClockSkewMs = 30 * 1000;
633
+ authenticatedWebRequests = new WeakSet;
627
634
  defaultAuthState = loadWebAuthStateFromEnvironment();
628
635
  });
629
636
 
@@ -9522,7 +9529,7 @@ var init_runtime16 = __esm(() => {
9522
9529
 
9523
9530
  // packages/ai/src/workspace-browser.ts
9524
9531
  import { z as z38 } from "zod";
9525
- var workspaceLocationIdSchema, workspaceLocationStateSchema, workspaceLocationSchema, workspaceLocationsResponseSchema, workspaceBrowserOpenRequestSchema, workspaceBrowserIdSchema, workspaceBrowserOpenResponseSchema, workspacePathSegmentSchema, workspacePathSegmentsSchema, workspaceBrowserCursorSchema, workspaceBrowserEntriesRequestSchema, workspaceBrowserEntryKindSchema, workspaceBrowserSymlinkStateSchema, workspaceBrowserEntrySchema, workspaceBrowserEntriesResponseSchema, workspaceBrowserSelectRequestSchema, workspaceGrantIdSchema, workspaceGrantSchema, workspaceBrowserSelectResponseSchema, workspaceSessionPathParamsSchema, workspaceSessionCreateRequestSchema, workspaceBrowserPathParamsSchema, workspaceApiErrorCodeSchema, workspaceApiErrorResponseSchema;
9532
+ var workspaceLocationIdSchema, workspaceLocationStateSchema, workspaceLocationSchema, workspaceLocationsResponseSchema, workspaceBrowserOpenRequestSchema, workspaceBrowserIdSchema, workspaceBrowserOpenResponseSchema, workspacePathSegmentSchema, workspacePathSegmentsSchema, workspaceBrowserCursorSchema, workspaceBrowserEntriesRequestSchema, workspaceBrowserEntryKindSchema, workspaceBrowserSymlinkStateSchema, workspaceBrowserEntrySchema, workspaceBrowserEntriesResponseSchema, workspaceBrowserSelectRequestSchema, workspaceGrantIdSchema, workspaceGrantSchema, workspaceNativePickerRequestSchema, workspaceNativePickerResponseSchema, workspaceBrowserSelectResponseSchema, workspaceSessionPathParamsSchema, workspaceSessionCreateRequestSchema, workspaceBrowserPathParamsSchema, workspaceApiErrorCodeSchema, workspaceApiErrorResponseSchema;
9526
9533
  var init_workspace_browser = __esm(() => {
9527
9534
  init_coding_agent_modes();
9528
9535
  init_permissions();
@@ -9604,6 +9611,16 @@ var init_workspace_browser = __esm(() => {
9604
9611
  pathLabel: z38.string().min(1).max(4096),
9605
9612
  createdAt: z38.string().min(1)
9606
9613
  }).strict();
9614
+ workspaceNativePickerRequestSchema = z38.object({}).strict();
9615
+ workspaceNativePickerResponseSchema = z38.discriminatedUnion("outcome", [
9616
+ z38.object({
9617
+ outcome: z38.literal("selected"),
9618
+ workspace: workspaceGrantSchema
9619
+ }).strict(),
9620
+ z38.object({
9621
+ outcome: z38.literal("cancelled")
9622
+ }).strict()
9623
+ ]);
9607
9624
  workspaceBrowserSelectResponseSchema = z38.object({
9608
9625
  workspace: workspaceGrantSchema
9609
9626
  }).strict();
@@ -9631,7 +9648,10 @@ var init_workspace_browser = __esm(() => {
9631
9648
  "browser_capability_expired",
9632
9649
  "workspace_grant_not_found",
9633
9650
  "invalid_cursor",
9634
- "workspace_unavailable"
9651
+ "workspace_unavailable",
9652
+ "native_picker_busy",
9653
+ "native_picker_unavailable",
9654
+ "native_picker_failed"
9635
9655
  ]);
9636
9656
  workspaceApiErrorResponseSchema = z38.object({
9637
9657
  error: z38.string().min(1),
@@ -16799,6 +16819,320 @@ var init_web_routes = __esm(() => {
16799
16819
  }).get("/app/*", serveWebAsset);
16800
16820
  });
16801
16821
 
16822
+ // apps/server/src/lib/native-directory-picker.ts
16823
+ import path24 from "path";
16824
+ import { accessSync, constants as fsConstants2 } from "fs";
16825
+ import { homedir as homedir4 } from "os";
16826
+ async function readBoundedStream(stream, maximumBytes) {
16827
+ const reader = stream.getReader();
16828
+ const chunks = [];
16829
+ let totalBytes = 0;
16830
+ try {
16831
+ while (true) {
16832
+ const { done, value } = await reader.read();
16833
+ if (done)
16834
+ break;
16835
+ totalBytes += value.byteLength;
16836
+ if (totalBytes > maximumBytes) {
16837
+ throw new PickerOutputTooLargeError;
16838
+ }
16839
+ chunks.push(value);
16840
+ }
16841
+ } finally {
16842
+ reader.releaseLock();
16843
+ }
16844
+ const output = new Uint8Array(totalBytes);
16845
+ let offset = 0;
16846
+ for (const chunk of chunks) {
16847
+ output.set(chunk, offset);
16848
+ offset += chunk.byteLength;
16849
+ }
16850
+ return new TextDecoder().decode(output);
16851
+ }
16852
+ function stripToolLineEnding(value) {
16853
+ return value.endsWith(`\r
16854
+ `) ? value.slice(0, -2) : value.endsWith(`
16855
+ `) ? value.slice(0, -1) : value;
16856
+ }
16857
+ function assertPickerPath(value, pathApi) {
16858
+ const windowsRoot = pathApi === path24.win32 ? path24.win32.parse(value).root : null;
16859
+ const isFullyQualifiedWindowsPath = windowsRoot === null || /^[A-Za-z]:[\\/]$/.test(windowsRoot) || /^\\\\(?![?.][\\/])[^\\/]+[\\/][^\\/]+[\\/]$/.test(windowsRoot);
16860
+ if (value.length === 0 || value.length > 4096 || value.includes("\x00") || !pathApi.isAbsolute(value) || !isFullyQualifiedWindowsPath) {
16861
+ throw new NativeDirectoryPickerError("native_picker_failed", "The system folder picker returned an invalid folder.");
16862
+ }
16863
+ return value;
16864
+ }
16865
+ function parseWindowsResult(result) {
16866
+ const output = stripToolLineEnding(result.stdout);
16867
+ if (result.exitCode !== 0) {
16868
+ throw new NativeDirectoryPickerError("native_picker_failed", "The Windows folder picker could not be opened.");
16869
+ }
16870
+ if (output === "cancelled")
16871
+ return { outcome: "cancelled" };
16872
+ if (!output.startsWith("selected:")) {
16873
+ throw new NativeDirectoryPickerError("native_picker_failed", "The Windows folder picker returned an invalid response.");
16874
+ }
16875
+ const encoded = output.slice("selected:".length);
16876
+ if (!encoded || !/^[A-Za-z0-9+/]+={0,2}$/.test(encoded)) {
16877
+ throw new NativeDirectoryPickerError("native_picker_failed", "The Windows folder picker returned an invalid response.");
16878
+ }
16879
+ try {
16880
+ const directory = new TextDecoder("utf-8", { fatal: true }).decode(Buffer.from(encoded, "base64"));
16881
+ if (Buffer.from(directory, "utf8").toString("base64").replace(/=+$/, "") !== encoded.replace(/=+$/, "")) {
16882
+ throw new Error("invalid base64");
16883
+ }
16884
+ return {
16885
+ outcome: "selected",
16886
+ directory: assertPickerPath(directory, path24.win32)
16887
+ };
16888
+ } catch (error) {
16889
+ if (error instanceof NativeDirectoryPickerError)
16890
+ throw error;
16891
+ throw new NativeDirectoryPickerError("native_picker_failed", "The Windows folder picker returned an invalid response.");
16892
+ }
16893
+ }
16894
+ function parseMacosResult(result) {
16895
+ if (result.exitCode !== 0) {
16896
+ if (/\(-128\)|user canceled/i.test(result.stderr)) {
16897
+ return { outcome: "cancelled" };
16898
+ }
16899
+ throw new NativeDirectoryPickerError("native_picker_failed", "The macOS folder picker could not be opened.");
16900
+ }
16901
+ return {
16902
+ outcome: "selected",
16903
+ directory: assertPickerPath(stripToolLineEnding(result.stdout), path24.posix)
16904
+ };
16905
+ }
16906
+ function parseLinuxResult(result) {
16907
+ if (result.exitCode === 1)
16908
+ return { outcome: "cancelled" };
16909
+ if (result.exitCode !== 0) {
16910
+ throw new NativeDirectoryPickerError("native_picker_failed", "The Linux folder picker could not be opened.");
16911
+ }
16912
+ return {
16913
+ outcome: "selected",
16914
+ directory: assertPickerPath(stripToolLineEnding(result.stdout), path24.posix)
16915
+ };
16916
+ }
16917
+ function resolvePickerCommand({
16918
+ platform: platform4,
16919
+ environment,
16920
+ which
16921
+ }) {
16922
+ if (platform4 === "win32") {
16923
+ const configuredRoot = environment.SystemRoot?.trim();
16924
+ const systemRoot = configuredRoot && path24.win32.isAbsolute(configuredRoot) ? configuredRoot : "C:\\Windows";
16925
+ const candidate = path24.win32.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
16926
+ const executable = which(candidate);
16927
+ if (!executable || !path24.win32.isAbsolute(executable))
16928
+ return null;
16929
+ return {
16930
+ kind: "windows",
16931
+ argv: [
16932
+ executable,
16933
+ "-NoLogo",
16934
+ "-NoProfile",
16935
+ "-NonInteractive",
16936
+ "-STA",
16937
+ "-EncodedCommand",
16938
+ Buffer.from(windowsPickerScript, "utf16le").toString("base64")
16939
+ ]
16940
+ };
16941
+ }
16942
+ if (platform4 === "darwin") {
16943
+ const executable = which("/usr/bin/osascript");
16944
+ if (!executable || !path24.posix.isAbsolute(executable))
16945
+ return null;
16946
+ return {
16947
+ kind: "macos",
16948
+ argv: [executable, "-e", macosPickerScript]
16949
+ };
16950
+ }
16951
+ if (platform4 === "linux") {
16952
+ if (!environment.DISPLAY?.trim() && !environment.WAYLAND_DISPLAY?.trim()) {
16953
+ return null;
16954
+ }
16955
+ const desktop = environment.XDG_CURRENT_DESKTOP?.toLowerCase() ?? "";
16956
+ const candidates = desktop.includes("kde") ? ["kdialog", "zenity"] : ["zenity", "kdialog"];
16957
+ for (const command of candidates) {
16958
+ let executable = null;
16959
+ for (const directory of trustedLinuxExecutableDirectories) {
16960
+ const candidate = path24.posix.join(directory, command);
16961
+ const resolved = which(candidate);
16962
+ if (resolved === candidate) {
16963
+ executable = resolved;
16964
+ break;
16965
+ }
16966
+ }
16967
+ if (!executable)
16968
+ continue;
16969
+ if (command === "zenity") {
16970
+ return {
16971
+ kind: "zenity",
16972
+ argv: [
16973
+ executable,
16974
+ "--file-selection",
16975
+ "--directory",
16976
+ "--title=Choose a project folder"
16977
+ ]
16978
+ };
16979
+ }
16980
+ return {
16981
+ kind: "kdialog",
16982
+ argv: [
16983
+ executable,
16984
+ "--title",
16985
+ "Choose a project folder",
16986
+ "--getexistingdirectory",
16987
+ path24.posix.isAbsolute(homedir4()) ? homedir4() : "/"
16988
+ ]
16989
+ };
16990
+ }
16991
+ }
16992
+ return null;
16993
+ }
16994
+ function createNativeDirectoryPicker(options = {}) {
16995
+ const platform4 = options.platform ?? process.platform;
16996
+ const environment = options.environment ?? Bun.env;
16997
+ const which = options.which ?? ((candidate) => {
16998
+ try {
16999
+ accessSync(candidate, fsConstants2.X_OK);
17000
+ return candidate;
17001
+ } catch {
17002
+ return null;
17003
+ }
17004
+ });
17005
+ const runProcess = options.runProcess ?? runNativePickerProcess;
17006
+ let active = false;
17007
+ return {
17008
+ async pick(signal) {
17009
+ if (active) {
17010
+ throw new NativeDirectoryPickerError("native_picker_busy", "A folder picker is already open.");
17011
+ }
17012
+ if (signal?.aborted)
17013
+ return { outcome: "cancelled" };
17014
+ const command = resolvePickerCommand({ platform: platform4, environment, which });
17015
+ if (!command) {
17016
+ throw new NativeDirectoryPickerError("native_picker_unavailable", "No supported system folder picker is available.");
17017
+ }
17018
+ active = true;
17019
+ try {
17020
+ const result = await runProcess(command.argv, signal);
17021
+ if (signal?.aborted)
17022
+ return { outcome: "cancelled" };
17023
+ if (command.kind === "windows")
17024
+ return parseWindowsResult(result);
17025
+ if (command.kind === "macos")
17026
+ return parseMacosResult(result);
17027
+ return parseLinuxResult(result);
17028
+ } catch (error) {
17029
+ if (error instanceof NativeDirectoryPickerError)
17030
+ throw error;
17031
+ throw new NativeDirectoryPickerError("native_picker_failed", "The system folder picker could not be opened.");
17032
+ } finally {
17033
+ active = false;
17034
+ }
17035
+ }
17036
+ };
17037
+ }
17038
+ var maximumPickerOutputBytes, NativeDirectoryPickerError, trustedLinuxExecutableDirectories, PickerOutputTooLargeError, runNativePickerProcess = async (command, signal) => {
17039
+ if (signal?.aborted) {
17040
+ return { exitCode: 1, stdout: "", stderr: "" };
17041
+ }
17042
+ const child = Bun.spawn([...command], {
17043
+ stdin: "ignore",
17044
+ stdout: "pipe",
17045
+ stderr: "pipe",
17046
+ windowsHide: true
17047
+ });
17048
+ let aborted = false;
17049
+ const abort = () => {
17050
+ aborted = true;
17051
+ try {
17052
+ child.kill();
17053
+ } catch {}
17054
+ };
17055
+ signal?.addEventListener("abort", abort, { once: true });
17056
+ if (signal?.aborted)
17057
+ abort();
17058
+ try {
17059
+ const [stdout, stderr, exitCode] = await Promise.all([
17060
+ readBoundedStream(child.stdout, maximumPickerOutputBytes),
17061
+ readBoundedStream(child.stderr, maximumPickerOutputBytes),
17062
+ child.exited
17063
+ ]);
17064
+ return {
17065
+ exitCode: aborted ? 1 : exitCode,
17066
+ stdout: aborted ? "" : stdout,
17067
+ stderr: aborted ? "" : stderr
17068
+ };
17069
+ } catch (error) {
17070
+ abort();
17071
+ if (error instanceof PickerOutputTooLargeError) {
17072
+ throw new NativeDirectoryPickerError("native_picker_failed", "The system folder picker returned an invalid response.");
17073
+ }
17074
+ throw error;
17075
+ } finally {
17076
+ signal?.removeEventListener("abort", abort);
17077
+ }
17078
+ }, windowsPickerScript, macosPickerScript;
17079
+ var init_native_directory_picker = __esm(() => {
17080
+ maximumPickerOutputBytes = 16 * 1024;
17081
+ NativeDirectoryPickerError = class NativeDirectoryPickerError extends Error {
17082
+ code;
17083
+ constructor(code, message) {
17084
+ super(message);
17085
+ this.code = code;
17086
+ this.name = "NativeDirectoryPickerError";
17087
+ }
17088
+ };
17089
+ trustedLinuxExecutableDirectories = [
17090
+ "/usr/bin",
17091
+ "/bin",
17092
+ "/run/current-system/sw/bin"
17093
+ ];
17094
+ PickerOutputTooLargeError = class PickerOutputTooLargeError extends Error {
17095
+ };
17096
+ windowsPickerScript = String.raw`
17097
+ $ErrorActionPreference = 'Stop'
17098
+ [Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)
17099
+ Add-Type -AssemblyName System.Windows.Forms
17100
+ $dialog = New-Object System.Windows.Forms.FolderBrowserDialog
17101
+ $dialog.Description = 'Choose a project folder'
17102
+ $dialog.ShowNewFolderButton = $true
17103
+ $dialog.AutoUpgradeEnabled = $true
17104
+ $owner = New-Object System.Windows.Forms.Form
17105
+ $owner.TopMost = $true
17106
+ $owner.ShowInTaskbar = $false
17107
+ $owner.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual
17108
+ $owner.Location = New-Object System.Drawing.Point(-32000, -32000)
17109
+ $owner.Size = New-Object System.Drawing.Size(1, 1)
17110
+ $owner.Opacity = 0
17111
+ try {
17112
+ $owner.Show()
17113
+ $owner.Activate()
17114
+ $result = $dialog.ShowDialog($owner)
17115
+ if ($result -eq [System.Windows.Forms.DialogResult]::OK) {
17116
+ $bytes = [Text.Encoding]::UTF8.GetBytes($dialog.SelectedPath)
17117
+ [Console]::Out.Write('selected:' + [Convert]::ToBase64String($bytes))
17118
+ } else {
17119
+ [Console]::Out.Write('cancelled')
17120
+ }
17121
+ } finally {
17122
+ $dialog.Dispose()
17123
+ $owner.Dispose()
17124
+ }
17125
+ `;
17126
+ macosPickerScript = String.raw`
17127
+ try
17128
+ set chosenFolder to choose folder with prompt "Choose a project folder"
17129
+ return POSIX path of chosenFolder
17130
+ on error number -128
17131
+ error number -128
17132
+ end try
17133
+ `;
17134
+ });
17135
+
16802
17136
  // apps/server/src/routes/workspace-routes.ts
16803
17137
  import { zValidator as zValidator3 } from "@hono/zod-validator";
16804
17138
  import {
@@ -16807,12 +17141,17 @@ import {
16807
17141
  randomBytes,
16808
17142
  timingSafeEqual as timingSafeEqual2
16809
17143
  } from "crypto";
16810
- import { constants as fsConstants2, promises as fs8 } from "fs";
16811
- import { homedir as homedir4 } from "os";
16812
- import path24 from "path";
17144
+ import { constants as fsConstants3, promises as fs8 } from "fs";
17145
+ import { homedir as homedir5 } from "os";
17146
+ import path25 from "path";
16813
17147
  import { Hono as Hono7 } from "hono";
16814
17148
  function pathImplementation(platform4) {
16815
- return platform4 === "win32" ? path24.win32 : path24.posix;
17149
+ return platform4 === "win32" ? path25.win32 : path25.posix;
17150
+ }
17151
+ function isFilesystemRootPath(candidate, platform4 = process.platform) {
17152
+ const pathApi = pathImplementation(platform4);
17153
+ const normalized = pathApi.normalize(candidate);
17154
+ return normalized === pathApi.parse(normalized).root;
16816
17155
  }
16817
17156
  function normalizeKnownFolderCandidate(candidate, platform4) {
16818
17157
  if (candidate.includes("\x00")) {
@@ -16823,7 +17162,7 @@ function normalizeKnownFolderCandidate(candidate, platform4) {
16823
17162
  return null;
16824
17163
  }
16825
17164
  const normalized = pathApi.normalize(candidate);
16826
- if (normalized === pathApi.parse(normalized).root) {
17165
+ if (isFilesystemRootPath(normalized, platform4)) {
16827
17166
  return null;
16828
17167
  }
16829
17168
  return normalized;
@@ -16841,10 +17180,10 @@ async function probeDirectory(candidate) {
16841
17180
  }
16842
17181
  }
16843
17182
  async function readSmallUserDirsConfiguration(filePath) {
16844
- const noFollowFlag = typeof fsConstants2.O_NOFOLLOW === "number" ? fsConstants2.O_NOFOLLOW : 0;
17183
+ const noFollowFlag = typeof fsConstants3.O_NOFOLLOW === "number" ? fsConstants3.O_NOFOLLOW : 0;
16845
17184
  let handle;
16846
17185
  try {
16847
- handle = await fs8.open(filePath, fsConstants2.O_RDONLY | noFollowFlag);
17186
+ handle = await fs8.open(filePath, fsConstants3.O_RDONLY | noFollowFlag);
16848
17187
  const metadata = await handle.stat();
16849
17188
  if (!metadata.isFile() || metadata.size <= 0 || metadata.size > maximumUserDirsConfigurationBytes) {
16850
17189
  return null;
@@ -16865,8 +17204,8 @@ async function resolveWindowsKnownFolder(environment, folder) {
16865
17204
  if (!windowsRoot) {
16866
17205
  return null;
16867
17206
  }
16868
- const systemDirectory = path24.win32.join(windowsRoot, "System32");
16869
- const executable = path24.win32.join(systemDirectory, "WindowsPowerShell", "v1.0", "powershell.exe");
17207
+ const systemDirectory = path25.win32.join(windowsRoot, "System32");
17208
+ const executable = path25.win32.join(systemDirectory, "WindowsPowerShell", "v1.0", "powershell.exe");
16870
17209
  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}')";
16871
17210
  const child = Bun.spawn([
16872
17211
  executable,
@@ -16925,15 +17264,15 @@ function parseXdgUserDirectory(contents, homeDirectory, variableName) {
16925
17264
  return null;
16926
17265
  }
16927
17266
  if (configured.startsWith("$HOME/")) {
16928
- return path24.posix.resolve(homeDirectory, configured.slice(6));
17267
+ return path25.posix.resolve(homeDirectory, configured.slice(6));
16929
17268
  }
16930
17269
  if (configured.startsWith("${HOME}/")) {
16931
- return path24.posix.resolve(homeDirectory, configured.slice(8));
17270
+ return path25.posix.resolve(homeDirectory, configured.slice(8));
16932
17271
  }
16933
17272
  if (configured.includes("$")) {
16934
17273
  return null;
16935
17274
  }
16936
- return path24.posix.isAbsolute(configured) ? path24.posix.normalize(configured) : null;
17275
+ return path25.posix.isAbsolute(configured) ? path25.posix.normalize(configured) : null;
16937
17276
  }
16938
17277
  return null;
16939
17278
  }
@@ -16963,7 +17302,7 @@ async function resolveDesktopDirectoryPath(options = {}) {
16963
17302
  const platform4 = options.platform ?? process.platform;
16964
17303
  const pathApi = pathImplementation(platform4);
16965
17304
  const environment = options.environment ?? Bun.env;
16966
- const homeDirectory = pathApi.normalize(options.homeDirectory ?? homedir4());
17305
+ const homeDirectory = pathApi.normalize(options.homeDirectory ?? homedir5());
16967
17306
  const explicit = environment.LIGHTCODE_DESKTOP_PATH?.trim();
16968
17307
  if (explicit) {
16969
17308
  const normalized = normalizeKnownFolderCandidate(explicit, platform4);
@@ -16987,17 +17326,17 @@ async function resolveDesktopDirectoryPath(options = {}) {
16987
17326
  return [];
16988
17327
  }
16989
17328
  return [
16990
- index === 0 ? trimmed : path24.win32.join(trimmed, "Desktop")
17329
+ index === 0 ? trimmed : path25.win32.join(trimmed, "Desktop")
16991
17330
  ];
16992
17331
  });
16993
- candidates.push(path24.win32.join(userProfile, "Desktop"));
16994
- return await firstUsableKnownFolder(candidates, platform4, probe) ?? path24.win32.join(homeDirectory, "Desktop");
17332
+ candidates.push(path25.win32.join(userProfile, "Desktop"));
17333
+ return await firstUsableKnownFolder(candidates, platform4, probe) ?? path25.win32.join(homeDirectory, "Desktop");
16995
17334
  }
16996
17335
  if (platform4 === "linux") {
16997
17336
  const configHome = environment.XDG_CONFIG_HOME?.trim();
16998
- const safeConfigHome = configHome && path24.posix.isAbsolute(configHome) ? path24.posix.normalize(configHome) : path24.posix.join(homeDirectory, ".config");
17337
+ const safeConfigHome = configHome && path25.posix.isAbsolute(configHome) ? path25.posix.normalize(configHome) : path25.posix.join(homeDirectory, ".config");
16999
17338
  const readConfiguration = options.readUserDirsConfiguration ?? readSmallUserDirsConfiguration;
17000
- const contents = await readConfiguration(path24.posix.join(safeConfigHome, "user-dirs.dirs"));
17339
+ const contents = await readConfiguration(path25.posix.join(safeConfigHome, "user-dirs.dirs"));
17001
17340
  const configured = contents ? parseXdgUserDirectory(contents, homeDirectory, "XDG_DESKTOP_DIR") : null;
17002
17341
  if (configured) {
17003
17342
  const selected = await firstUsableKnownFolder([configured], platform4, probe);
@@ -17012,7 +17351,7 @@ async function resolveUserDirectoryPath(folder, options) {
17012
17351
  const platform4 = options.platform ?? process.platform;
17013
17352
  const pathApi = pathImplementation(platform4);
17014
17353
  const environment = options.environment ?? Bun.env;
17015
- const homeDirectory = pathApi.normalize(options.homeDirectory ?? homedir4());
17354
+ const homeDirectory = pathApi.normalize(options.homeDirectory ?? homedir5());
17016
17355
  const displayName = folder === "documents" ? "Documents" : "Downloads";
17017
17356
  const overrideName = folder === "documents" ? "LIGHTCODE_DOCUMENTS_PATH" : "LIGHTCODE_DOWNLOADS_PATH";
17018
17357
  const explicit = environment[overrideName]?.trim();
@@ -17034,17 +17373,17 @@ async function resolveUserDirectoryPath(folder, options) {
17034
17373
  ];
17035
17374
  const candidates = [
17036
17375
  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)
17376
+ ...cloudRoots.map((root) => root?.trim() ? path25.win32.join(root.trim(), displayName) : null),
17377
+ path25.win32.join(userProfile, displayName),
17378
+ path25.win32.join(homeDirectory, displayName)
17040
17379
  ];
17041
- return await firstUsableKnownFolder(candidates, platform4, probe) ?? path24.win32.join(homeDirectory, displayName);
17380
+ return await firstUsableKnownFolder(candidates, platform4, probe) ?? path25.win32.join(homeDirectory, displayName);
17042
17381
  }
17043
17382
  if (platform4 === "linux") {
17044
17383
  const configHome = environment.XDG_CONFIG_HOME?.trim();
17045
- const safeConfigHome = configHome && path24.posix.isAbsolute(configHome) ? path24.posix.normalize(configHome) : path24.posix.join(homeDirectory, ".config");
17384
+ const safeConfigHome = configHome && path25.posix.isAbsolute(configHome) ? path25.posix.normalize(configHome) : path25.posix.join(homeDirectory, ".config");
17046
17385
  const readConfiguration = options.readUserDirsConfiguration ?? readSmallUserDirsConfiguration;
17047
- const contents = await readConfiguration(path24.posix.join(safeConfigHome, "user-dirs.dirs"));
17386
+ const contents = await readConfiguration(path25.posix.join(safeConfigHome, "user-dirs.dirs"));
17048
17387
  const configured = contents ? parseXdgUserDirectory(contents, homeDirectory, folder === "documents" ? "XDG_DOCUMENTS_DIR" : "XDG_DOWNLOAD_DIR") : null;
17049
17388
  const selected = await firstUsableKnownFolder([configured], platform4, probe);
17050
17389
  if (selected) {
@@ -17063,7 +17402,7 @@ async function resolveProjectsDirectoryPath(options = {}) {
17063
17402
  const platform4 = options.platform ?? process.platform;
17064
17403
  const pathApi = pathImplementation(platform4);
17065
17404
  const environment = options.environment ?? Bun.env;
17066
- const homeDirectory = pathApi.normalize(options.homeDirectory ?? homedir4());
17405
+ const homeDirectory = pathApi.normalize(options.homeDirectory ?? homedir5());
17067
17406
  const explicit = environment.LIGHTCODE_PROJECTS_PATH?.trim();
17068
17407
  if (explicit) {
17069
17408
  const normalized = normalizeKnownFolderCandidate(explicit, platform4);
@@ -17114,11 +17453,23 @@ function mapFilesystemError(error) {
17114
17453
  }
17115
17454
  return new WorkspaceRouteError("workspace_unavailable", "Unable to access the selected workspace.", 500, true);
17116
17455
  }
17456
+ function mapNativePickerError(error) {
17457
+ if (!(error instanceof NativeDirectoryPickerError)) {
17458
+ return new WorkspaceRouteError("native_picker_failed", "The system folder picker could not be opened.", 500, true);
17459
+ }
17460
+ if (error.code === "native_picker_busy") {
17461
+ return new WorkspaceRouteError(error.code, "A folder picker is already open.", 409, true);
17462
+ }
17463
+ if (error.code === "native_picker_unavailable") {
17464
+ return new WorkspaceRouteError(error.code, "No supported system folder picker is available.", 503, false);
17465
+ }
17466
+ return new WorkspaceRouteError(error.code, "The system folder picker could not be opened.", 500, true);
17467
+ }
17117
17468
  function workspaceErrorResponse(c, error) {
17118
17469
  const mapped = error instanceof WorkspaceRouteError ? error : mapFilesystemError(error);
17119
17470
  if (mapped.status === 500) {
17120
17471
  logger13.error("workspace_request_failed", {
17121
- error: getErrorMessage(error)
17472
+ error: getErrorMessage(mapped)
17122
17473
  });
17123
17474
  }
17124
17475
  return c.json({
@@ -17144,13 +17495,13 @@ function identitiesMatch(left, right) {
17144
17495
  return left.device === right.device && left.inode === right.inode;
17145
17496
  }
17146
17497
  function isContainedPath2(root, candidate) {
17147
- const relative = path24.relative(root, candidate);
17148
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path24.sep}`) && !path24.isAbsolute(relative);
17498
+ const relative = path25.relative(root, candidate);
17499
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path25.sep}`) && !path25.isAbsolute(relative);
17149
17500
  }
17150
17501
  async function resolveKnownDirectory(directory) {
17151
17502
  try {
17152
- const root = await fs8.realpath(path24.resolve(directory));
17153
- if (root === path24.parse(root).root) {
17503
+ const root = await fs8.realpath(path25.resolve(directory));
17504
+ if (isFilesystemRootPath(root)) {
17154
17505
  throw new WorkspaceRouteError("workspace_unavailable", "A filesystem root cannot be opened as a workspace location.", 500, false);
17155
17506
  }
17156
17507
  const stats = await fs8.stat(root, { bigint: true });
@@ -17199,7 +17550,7 @@ async function symlinkFailure(root, candidate, locationName) {
17199
17550
  async function resolveDirectoryFromSegments(root, segments, locationName) {
17200
17551
  let current = root;
17201
17552
  for (const segment of segments) {
17202
- const candidate = path24.join(current, segment);
17553
+ const candidate = path25.join(current, segment);
17203
17554
  if (!isContainedPath2(root, candidate)) {
17204
17555
  throw new WorkspaceRouteError("workspace_symlink_escape", `The requested path is outside the allowed ${locationName} location.`, 403, false);
17205
17556
  }
@@ -17302,7 +17653,7 @@ function decodeCursor(secret, cursor, binding) {
17302
17653
  }
17303
17654
  }
17304
17655
  async function describeEntry(root, directory, name) {
17305
- const candidate = path24.join(directory, name);
17656
+ const candidate = path25.join(directory, name);
17306
17657
  try {
17307
17658
  const stats = await fs8.lstat(candidate);
17308
17659
  if (stats.isSymbolicLink()) {
@@ -17326,7 +17677,7 @@ async function describeEntry(root, directory, name) {
17326
17677
  }
17327
17678
  }
17328
17679
  const kind = stats.isDirectory() ? "directory" : stats.isFile() ? "file" : "other";
17329
- const requiredAccess = stats.isDirectory() ? fsConstants2.R_OK | fsConstants2.X_OK : fsConstants2.R_OK;
17680
+ const requiredAccess = stats.isDirectory() ? fsConstants3.R_OK | fsConstants3.X_OK : fsConstants3.R_OK;
17330
17681
  const readable = await fs8.access(candidate, requiredAccess).then(() => true).catch(() => false);
17331
17682
  return {
17332
17683
  name,
@@ -17382,7 +17733,7 @@ function createWorkspaceRoutes(options = {}) {
17382
17733
  return normalized;
17383
17734
  } : () => resolveDesktopDirectoryPath());
17384
17735
  const desktopPathLabel = options.desktopPathLabel ?? "Desktop";
17385
- const defaultHomePath = async () => homedir4();
17736
+ const defaultHomePath = async () => homedir5();
17386
17737
  const defaultDocumentsPath = () => resolveDocumentsDirectoryPath();
17387
17738
  const defaultDownloadsPath = () => resolveDownloadsDirectoryPath();
17388
17739
  const definitions = [
@@ -17424,6 +17775,8 @@ function createWorkspaceRoutes(options = {}) {
17424
17775
  ];
17425
17776
  const definitionsById = new Map(definitions.map((definition) => [definition.id, definition]));
17426
17777
  const createSession = options.createSession ?? createChatSession;
17778
+ const nativeDirectoryPicker = options.nativeDirectoryPicker ?? createNativeDirectoryPicker();
17779
+ const authorizeNativePickerRequest = options.authorizeNativePickerRequest ?? isAuthenticatedWebRequest;
17427
17780
  const cursorSecret = randomBytes(32);
17428
17781
  const browserCapabilities = new Map;
17429
17782
  const workspaceGrants = new Map;
@@ -17442,7 +17795,70 @@ function createWorkspaceRoutes(options = {}) {
17442
17795
  }
17443
17796
  return grant;
17444
17797
  };
17445
- return new Hono7().get("/locations", (c) => c.json(workspaceLocationsResponseSchema.parse({
17798
+ const storeWorkspaceGrant = async ({
17799
+ root,
17800
+ identity,
17801
+ name,
17802
+ pathLabel
17803
+ }) => {
17804
+ await fs8.access(root, fsConstants3.R_OK | fsConstants3.X_OK);
17805
+ const createdAt = new Date(now()).toISOString();
17806
+ const id = crypto.randomUUID();
17807
+ const grant = {
17808
+ id,
17809
+ name,
17810
+ pathLabel,
17811
+ createdAt,
17812
+ root,
17813
+ identity
17814
+ };
17815
+ workspaceGrants.set(id, grant);
17816
+ trimOldestEntries(workspaceGrants, maximumWorkspaceGrants);
17817
+ try {
17818
+ await assertRootIdentity(grant);
17819
+ } catch (error) {
17820
+ workspaceGrants.delete(id);
17821
+ throw error;
17822
+ }
17823
+ return workspaceGrantSchema.parse({ id, name, pathLabel, createdAt });
17824
+ };
17825
+ return new Hono7().post("/picker/open", zValidator3("json", workspaceNativePickerRequestSchema, (result, c) => {
17826
+ if (!result.success) {
17827
+ return invalidRequestResponse(c);
17828
+ }
17829
+ }), async (c) => {
17830
+ if (!authorizeNativePickerRequest(c.req.raw)) {
17831
+ return c.json({
17832
+ error: "The system folder picker is available only to an authenticated Lightcode web session.",
17833
+ code: "web_browser_required"
17834
+ }, 403);
17835
+ }
17836
+ try {
17837
+ let picked;
17838
+ try {
17839
+ picked = await nativeDirectoryPicker.pick(c.req.raw.signal);
17840
+ } catch (error) {
17841
+ throw mapNativePickerError(error);
17842
+ }
17843
+ if (picked.outcome === "cancelled") {
17844
+ return c.json(workspaceNativePickerResponseSchema.parse({
17845
+ outcome: "cancelled"
17846
+ }), 200);
17847
+ }
17848
+ const directory = await resolveKnownDirectory(picked.directory);
17849
+ const workspace = await storeWorkspaceGrant({
17850
+ ...directory,
17851
+ name: path25.basename(directory.root),
17852
+ pathLabel: directory.root
17853
+ });
17854
+ return c.json(workspaceNativePickerResponseSchema.parse({
17855
+ outcome: "selected",
17856
+ workspace
17857
+ }), 201);
17858
+ } catch (error) {
17859
+ return workspaceErrorResponse(c, error);
17860
+ }
17861
+ }).get("/locations", (c) => c.json(workspaceLocationsResponseSchema.parse({
17446
17862
  locations: definitions.map((definition) => knownLocation(definition, "unprobed"))
17447
17863
  }))).post("/browser/open", zValidator3("json", workspaceBrowserOpenRequestSchema, (result, c) => {
17448
17864
  if (!result.success) {
@@ -17578,37 +17994,19 @@ function createWorkspaceRoutes(options = {}) {
17578
17994
  operation: "select",
17579
17995
  directory: directory.path
17580
17996
  });
17581
- await fs8.access(directory.path, fsConstants2.R_OK | fsConstants2.X_OK);
17582
- const createdAt = new Date(now()).toISOString();
17583
- const id = crypto.randomUUID();
17584
17997
  const name = segments.at(-1) ?? capability2.rootName;
17585
17998
  const pathLabel = [
17586
17999
  capability2.location.pathLabel,
17587
18000
  ...segments
17588
18001
  ].join("/");
17589
- const grant = {
17590
- id,
17591
- name,
17592
- pathLabel,
17593
- createdAt,
18002
+ const workspace = await storeWorkspaceGrant({
17594
18003
  root: directory.path,
17595
- identity: directory.identity
17596
- };
17597
- workspaceGrants.set(id, grant);
17598
- trimOldestEntries(workspaceGrants, maximumWorkspaceGrants);
17599
- try {
17600
- await assertRootIdentity(grant);
17601
- } catch (error) {
17602
- workspaceGrants.delete(id);
17603
- throw error;
17604
- }
18004
+ identity: directory.identity,
18005
+ name,
18006
+ pathLabel
18007
+ });
17605
18008
  return c.json(workspaceBrowserSelectResponseSchema.parse({
17606
- workspace: workspaceGrantSchema.parse({
17607
- id,
17608
- name,
17609
- pathLabel,
17610
- createdAt
17611
- })
18009
+ workspace
17612
18010
  }), 201);
17613
18011
  } catch (error) {
17614
18012
  return workspaceErrorResponse(c, error);
@@ -17646,6 +18044,8 @@ var init_workspace_routes = __esm(() => {
17646
18044
  init_src();
17647
18045
  init_chat_store();
17648
18046
  init_runtime_config();
18047
+ init_native_directory_picker();
18048
+ init_web_auth();
17649
18049
  logger13 = createLogger("workspace-routes");
17650
18050
  defaultBrowserCapabilityTtlMs = 10 * 60 * 1000;
17651
18051
  maximumUserDirsConfigurationBytes = 16 * 1024;