@fractary/codex 0.8.0 → 0.9.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/dist/index.cjs CHANGED
@@ -5,15 +5,33 @@ var path3 = require('path');
5
5
  var child_process = require('child_process');
6
6
  var zod = require('zod');
7
7
  var yaml = require('js-yaml');
8
- var fs2 = require('fs/promises');
8
+ var fs3 = require('fs/promises');
9
9
  var util = require('util');
10
10
 
11
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
12
 
13
+ function _interopNamespace(e) {
14
+ if (e && e.__esModule) return e;
15
+ var n = Object.create(null);
16
+ if (e) {
17
+ Object.keys(e).forEach(function (k) {
18
+ if (k !== 'default') {
19
+ var d = Object.getOwnPropertyDescriptor(e, k);
20
+ Object.defineProperty(n, k, d.get ? d : {
21
+ enumerable: true,
22
+ get: function () { return e[k]; }
23
+ });
24
+ }
25
+ });
26
+ }
27
+ n.default = e;
28
+ return Object.freeze(n);
29
+ }
30
+
13
31
  var micromatch3__default = /*#__PURE__*/_interopDefault(micromatch3);
14
- var path3__default = /*#__PURE__*/_interopDefault(path3);
32
+ var path3__namespace = /*#__PURE__*/_interopNamespace(path3);
15
33
  var yaml__default = /*#__PURE__*/_interopDefault(yaml);
16
- var fs2__default = /*#__PURE__*/_interopDefault(fs2);
34
+ var fs3__namespace = /*#__PURE__*/_interopNamespace(fs3);
17
35
 
18
36
  var __defProp = Object.defineProperty;
19
37
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -170,7 +188,7 @@ function validatePath(filePath) {
170
188
  return false;
171
189
  }
172
190
  }
173
- const normalized = path3__default.default.normalize(filePath);
191
+ const normalized = path3__namespace.default.normalize(filePath);
174
192
  if (normalized.startsWith("..") || normalized.includes("/../") || normalized.includes("\\..\\")) {
175
193
  return false;
176
194
  }
@@ -188,7 +206,7 @@ function sanitizePath(filePath) {
188
206
  }
189
207
  sanitized = sanitized.replace(/^\/+/, "");
190
208
  sanitized = sanitized.replace(/^~\//, "");
191
- sanitized = path3__default.default.normalize(sanitized);
209
+ sanitized = path3__namespace.default.normalize(sanitized);
192
210
  sanitized = sanitized.replace(/\.\.\//g, "").replace(/\.\./g, "");
193
211
  sanitized = sanitized.replace(/^\.\//, "");
194
212
  sanitized = sanitized.replace(/\0/g, "");
@@ -289,10 +307,10 @@ function parseReference(uri, options = {}) {
289
307
  path: filePath
290
308
  };
291
309
  }
292
- function buildUri(org, project, path6) {
310
+ function buildUri(org, project, path7) {
293
311
  const base = `${CODEX_URI_PREFIX}${org}/${project}`;
294
- if (path6) {
295
- const cleanPath = path6.startsWith("/") ? path6.slice(1) : path6;
312
+ if (path7) {
313
+ const cleanPath = path7.startsWith("/") ? path7.slice(1) : path7;
296
314
  return `${base}/${cleanPath}`;
297
315
  }
298
316
  return base;
@@ -363,7 +381,7 @@ function getCurrentContext(options = {}) {
363
381
  return detectCurrentProject(options.cwd);
364
382
  }
365
383
  function calculateCachePath(org, project, filePath, cacheDir) {
366
- return path3__default.default.join(cacheDir, org, project, filePath);
384
+ return path3__namespace.default.join(cacheDir, org, project, filePath);
367
385
  }
368
386
  function resolveReference(uri, options = {}) {
369
387
  const parsed = parseReference(uri);
@@ -372,7 +390,7 @@ function resolveReference(uri, options = {}) {
372
390
  }
373
391
  const cacheDir = options.cacheDir || DEFAULT_CACHE_DIR;
374
392
  const currentContext = getCurrentContext(options);
375
- const cachePath = parsed.path ? calculateCachePath(parsed.org, parsed.project, parsed.path, cacheDir) : path3__default.default.join(cacheDir, parsed.org, parsed.project);
393
+ const cachePath = parsed.path ? calculateCachePath(parsed.org, parsed.project, parsed.path, cacheDir) : path3__namespace.default.join(cacheDir, parsed.org, parsed.project);
376
394
  const isCurrentProject = currentContext.org === parsed.org && currentContext.project === parsed.project;
377
395
  const resolved = {
378
396
  ...parsed,
@@ -381,9 +399,45 @@ function resolveReference(uri, options = {}) {
381
399
  };
382
400
  if (isCurrentProject && parsed.path) {
383
401
  resolved.localPath = parsed.path;
402
+ if (options.config) {
403
+ const fileSource = detectFilePluginSource(parsed.path, options.config);
404
+ if (fileSource) {
405
+ resolved.sourceType = "file-plugin";
406
+ resolved.filePluginSource = fileSource.name;
407
+ resolved.localPath = fileSource.fullPath;
408
+ }
409
+ }
384
410
  }
385
411
  return resolved;
386
412
  }
413
+ function detectFilePluginSource(filePath, config) {
414
+ if (!config?.file?.sources) {
415
+ return null;
416
+ }
417
+ for (const [sourceName, source] of Object.entries(config.file.sources)) {
418
+ const basePath = source.local?.base_path;
419
+ if (!basePath) {
420
+ continue;
421
+ }
422
+ const normalizedPath = filePath.replace(/^\.\//, "").replace(/^\//, "");
423
+ const normalizedBasePath = basePath.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "");
424
+ if (normalizedPath.startsWith(normalizedBasePath)) {
425
+ return {
426
+ name: sourceName,
427
+ fullPath: path3__namespace.default.join(basePath, normalizedPath.substring(normalizedBasePath.length).replace(/^\//, ""))
428
+ };
429
+ }
430
+ const sourceNameInPath = normalizedBasePath.split("/").pop();
431
+ if (sourceNameInPath && normalizedPath.startsWith(sourceNameInPath + "/")) {
432
+ const pathWithoutSource = normalizedPath.substring(sourceNameInPath.length + 1);
433
+ return {
434
+ name: sourceName,
435
+ fullPath: path3__namespace.default.join(basePath, pathWithoutSource)
436
+ };
437
+ }
438
+ }
439
+ return null;
440
+ }
387
441
  function resolveReferences(uris, options = {}) {
388
442
  return uris.map((uri) => resolveReference(uri, options)).filter((r) => r !== null);
389
443
  }
@@ -397,9 +451,9 @@ function getRelativeCachePath(uri) {
397
451
  return null;
398
452
  }
399
453
  if (parsed.path) {
400
- return path3__default.default.join(parsed.org, parsed.project, parsed.path);
454
+ return path3__namespace.default.join(parsed.org, parsed.project, parsed.path);
401
455
  }
402
- return path3__default.default.join(parsed.org, parsed.project);
456
+ return path3__namespace.default.join(parsed.org, parsed.project);
403
457
  }
404
458
 
405
459
  // src/types/built-in.ts
@@ -867,6 +921,41 @@ var CodexConfigSchema = zod.z.object({
867
921
  // Archive configuration
868
922
  archive: ArchiveConfigSchema.optional()
869
923
  }).strict();
924
+ var FileSourceSchema = zod.z.object({
925
+ type: zod.z.enum(["s3", "r2", "gcs", "local"]),
926
+ bucket: zod.z.string().optional(),
927
+ prefix: zod.z.string().optional(),
928
+ region: zod.z.string().optional(),
929
+ local: zod.z.object({
930
+ base_path: zod.z.string()
931
+ }),
932
+ push: zod.z.object({
933
+ compress: zod.z.boolean().optional(),
934
+ keep_local: zod.z.boolean().optional()
935
+ }).optional(),
936
+ auth: zod.z.object({
937
+ profile: zod.z.string().optional()
938
+ }).optional()
939
+ }).refine(
940
+ (data) => {
941
+ if (data.type !== "local" && !data.bucket) {
942
+ return false;
943
+ }
944
+ return true;
945
+ },
946
+ {
947
+ message: "Bucket is required for s3, r2, and gcs storage types",
948
+ path: ["bucket"]
949
+ }
950
+ );
951
+ var FileConfigSchema = zod.z.object({
952
+ schema_version: zod.z.string(),
953
+ sources: zod.z.record(FileSourceSchema)
954
+ });
955
+ zod.z.object({
956
+ file: FileConfigSchema.optional(),
957
+ codex: CodexConfigSchema.optional()
958
+ });
870
959
  function parseMetadata(content, options = {}) {
871
960
  const { strict = true, normalize = true } = options;
872
961
  const normalizedContent = normalize ? content.replace(/\r\n/g, "\n") : content;
@@ -1196,18 +1285,18 @@ function parseCustomDestination(value) {
1196
1285
  );
1197
1286
  }
1198
1287
  const repo = value.substring(0, colonIndex).trim();
1199
- const path6 = value.substring(colonIndex + 1).trim();
1288
+ const path7 = value.substring(colonIndex + 1).trim();
1200
1289
  if (!repo) {
1201
1290
  throw new ValidationError(
1202
1291
  `Invalid custom destination: repository name cannot be empty in "${value}"`
1203
1292
  );
1204
1293
  }
1205
- if (!path6) {
1294
+ if (!path7) {
1206
1295
  throw new ValidationError(
1207
1296
  `Invalid custom destination: path cannot be empty in "${value}"`
1208
1297
  );
1209
1298
  }
1210
- return { repo, path: path6 };
1299
+ return { repo, path: path7 };
1211
1300
  }
1212
1301
  function getCustomSyncDestinations(metadata) {
1213
1302
  const customDestinations = metadata.codex_sync_custom;
@@ -1243,8 +1332,8 @@ function mergeFetchOptions(options) {
1243
1332
  ...options
1244
1333
  };
1245
1334
  }
1246
- function detectContentType(path6) {
1247
- const ext = path6.split(".").pop()?.toLowerCase();
1335
+ function detectContentType(path7) {
1336
+ const ext = path7.split(".").pop()?.toLowerCase();
1248
1337
  const mimeTypes = {
1249
1338
  md: "text/markdown",
1250
1339
  markdown: "text/markdown",
@@ -1304,19 +1393,19 @@ var LocalStorage = class {
1304
1393
  if (!reference.localPath) {
1305
1394
  throw new Error(`No local path for reference: ${reference.uri}`);
1306
1395
  }
1307
- const fullPath = path3__default.default.isAbsolute(reference.localPath) ? reference.localPath : path3__default.default.join(this.baseDir, reference.localPath);
1396
+ const fullPath = path3__namespace.default.isAbsolute(reference.localPath) ? reference.localPath : path3__namespace.default.join(this.baseDir, reference.localPath);
1308
1397
  try {
1309
- await fs2__default.default.access(fullPath);
1398
+ await fs3__namespace.default.access(fullPath);
1310
1399
  } catch {
1311
1400
  throw new Error(`File not found: ${fullPath}`);
1312
1401
  }
1313
- const stats = await fs2__default.default.stat(fullPath);
1402
+ const stats = await fs3__namespace.default.stat(fullPath);
1314
1403
  if (stats.size > opts.maxSize) {
1315
1404
  throw new Error(
1316
1405
  `File too large: ${stats.size} bytes (max: ${opts.maxSize} bytes)`
1317
1406
  );
1318
1407
  }
1319
- const content = await fs2__default.default.readFile(fullPath);
1408
+ const content = await fs3__namespace.default.readFile(fullPath);
1320
1409
  return {
1321
1410
  content,
1322
1411
  contentType: detectContentType(reference.localPath),
@@ -1335,9 +1424,9 @@ var LocalStorage = class {
1335
1424
  if (!reference.localPath) {
1336
1425
  return false;
1337
1426
  }
1338
- const fullPath = path3__default.default.isAbsolute(reference.localPath) ? reference.localPath : path3__default.default.join(this.baseDir, reference.localPath);
1427
+ const fullPath = path3__namespace.default.isAbsolute(reference.localPath) ? reference.localPath : path3__namespace.default.join(this.baseDir, reference.localPath);
1339
1428
  try {
1340
- await fs2__default.default.access(fullPath);
1429
+ await fs3__namespace.default.access(fullPath);
1341
1430
  return true;
1342
1431
  } catch {
1343
1432
  return false;
@@ -1347,24 +1436,24 @@ var LocalStorage = class {
1347
1436
  * Read file content as string
1348
1437
  */
1349
1438
  async readText(filePath) {
1350
- const fullPath = path3__default.default.isAbsolute(filePath) ? filePath : path3__default.default.join(this.baseDir, filePath);
1351
- return fs2__default.default.readFile(fullPath, "utf-8");
1439
+ const fullPath = path3__namespace.default.isAbsolute(filePath) ? filePath : path3__namespace.default.join(this.baseDir, filePath);
1440
+ return fs3__namespace.default.readFile(fullPath, "utf-8");
1352
1441
  }
1353
1442
  /**
1354
1443
  * Write content to file
1355
1444
  */
1356
1445
  async write(filePath, content) {
1357
- const fullPath = path3__default.default.isAbsolute(filePath) ? filePath : path3__default.default.join(this.baseDir, filePath);
1358
- await fs2__default.default.mkdir(path3__default.default.dirname(fullPath), { recursive: true });
1359
- await fs2__default.default.writeFile(fullPath, content);
1446
+ const fullPath = path3__namespace.default.isAbsolute(filePath) ? filePath : path3__namespace.default.join(this.baseDir, filePath);
1447
+ await fs3__namespace.default.mkdir(path3__namespace.default.dirname(fullPath), { recursive: true });
1448
+ await fs3__namespace.default.writeFile(fullPath, content);
1360
1449
  }
1361
1450
  /**
1362
1451
  * Delete a file
1363
1452
  */
1364
1453
  async delete(filePath) {
1365
- const fullPath = path3__default.default.isAbsolute(filePath) ? filePath : path3__default.default.join(this.baseDir, filePath);
1454
+ const fullPath = path3__namespace.default.isAbsolute(filePath) ? filePath : path3__namespace.default.join(this.baseDir, filePath);
1366
1455
  try {
1367
- await fs2__default.default.unlink(fullPath);
1456
+ await fs3__namespace.default.unlink(fullPath);
1368
1457
  return true;
1369
1458
  } catch {
1370
1459
  return false;
@@ -1374,10 +1463,10 @@ var LocalStorage = class {
1374
1463
  * List files in a directory
1375
1464
  */
1376
1465
  async list(dirPath) {
1377
- const fullPath = path3__default.default.isAbsolute(dirPath) ? dirPath : path3__default.default.join(this.baseDir, dirPath);
1466
+ const fullPath = path3__namespace.default.isAbsolute(dirPath) ? dirPath : path3__namespace.default.join(this.baseDir, dirPath);
1378
1467
  try {
1379
- const entries = await fs2__default.default.readdir(fullPath, { withFileTypes: true });
1380
- return entries.filter((e) => e.isFile()).map((e) => path3__default.default.join(dirPath, e.name));
1468
+ const entries = await fs3__namespace.default.readdir(fullPath, { withFileTypes: true });
1469
+ return entries.filter((e) => e.isFile()).map((e) => path3__namespace.default.join(dirPath, e.name));
1381
1470
  } catch {
1382
1471
  return [];
1383
1472
  }
@@ -1722,6 +1811,300 @@ var HttpStorage = class {
1722
1811
  function createHttpStorage(options) {
1723
1812
  return new HttpStorage(options);
1724
1813
  }
1814
+
1815
+ // src/file-integration/source-resolver.ts
1816
+ var FileSourceResolver = class {
1817
+ constructor(config) {
1818
+ this.config = config;
1819
+ this.initializeSources();
1820
+ }
1821
+ sources = /* @__PURE__ */ new Map();
1822
+ /**
1823
+ * Initialize sources from config
1824
+ */
1825
+ initializeSources() {
1826
+ if (!this.config.file?.sources) {
1827
+ return;
1828
+ }
1829
+ for (const [name, sourceConfig] of Object.entries(this.config.file.sources)) {
1830
+ const resolved = {
1831
+ name,
1832
+ type: "file-plugin",
1833
+ localPath: sourceConfig.local.base_path,
1834
+ isCurrentProject: true,
1835
+ config: sourceConfig
1836
+ };
1837
+ if (sourceConfig.bucket && sourceConfig.type !== "local") {
1838
+ resolved.bucket = sourceConfig.bucket;
1839
+ resolved.prefix = sourceConfig.prefix;
1840
+ resolved.remotePath = this.buildRemotePath(sourceConfig);
1841
+ }
1842
+ this.sources.set(name, resolved);
1843
+ }
1844
+ }
1845
+ /**
1846
+ * Build remote path from source config
1847
+ */
1848
+ buildRemotePath(source) {
1849
+ const protocol = source.type === "s3" ? "s3://" : source.type === "r2" ? "r2://" : "gcs://";
1850
+ const bucket = source.bucket;
1851
+ const prefix = source.prefix ? `/${source.prefix}` : "";
1852
+ return `${protocol}${bucket}${prefix}`;
1853
+ }
1854
+ /**
1855
+ * Get all available file plugin sources
1856
+ *
1857
+ * @returns Array of resolved file sources
1858
+ */
1859
+ getAvailableSources() {
1860
+ return Array.from(this.sources.values());
1861
+ }
1862
+ /**
1863
+ * Resolve a source by name
1864
+ *
1865
+ * @param name - Source name (e.g., "specs", "logs")
1866
+ * @returns Resolved source or null if not found
1867
+ */
1868
+ resolveSource(name) {
1869
+ return this.sources.get(name) || null;
1870
+ }
1871
+ /**
1872
+ * Check if a path belongs to any file plugin source
1873
+ *
1874
+ * @param path - File path to check
1875
+ * @returns True if path matches any source's base_path
1876
+ */
1877
+ isFilePluginPath(path7) {
1878
+ return this.getSourceForPath(path7) !== null;
1879
+ }
1880
+ /**
1881
+ * Get the source for a given path
1882
+ *
1883
+ * Matches path against all source base_paths and returns the matching source.
1884
+ * Uses longest-match strategy if multiple sources match.
1885
+ *
1886
+ * @param path - File path to match
1887
+ * @returns Matching source or null
1888
+ */
1889
+ getSourceForPath(path7) {
1890
+ let bestMatch = null;
1891
+ let bestMatchLength = 0;
1892
+ for (const source of this.sources.values()) {
1893
+ const normalizedPath = this.normalizePath(path7);
1894
+ const normalizedBasePath = this.normalizePath(source.localPath);
1895
+ if (normalizedPath.startsWith(normalizedBasePath)) {
1896
+ const matchLength = normalizedBasePath.length;
1897
+ if (matchLength > bestMatchLength) {
1898
+ bestMatch = source;
1899
+ bestMatchLength = matchLength;
1900
+ }
1901
+ }
1902
+ }
1903
+ return bestMatch;
1904
+ }
1905
+ /**
1906
+ * Normalize path for comparison
1907
+ * - Remove leading "./" or "/"
1908
+ * - Remove trailing "/"
1909
+ * - Convert to lowercase for case-insensitive comparison
1910
+ */
1911
+ normalizePath(path7) {
1912
+ return path7.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "").toLowerCase();
1913
+ }
1914
+ /**
1915
+ * Get source names
1916
+ *
1917
+ * @returns Array of source names
1918
+ */
1919
+ getSourceNames() {
1920
+ return Array.from(this.sources.keys());
1921
+ }
1922
+ /**
1923
+ * Check if sources are configured
1924
+ *
1925
+ * @returns True if any file plugin sources are configured
1926
+ */
1927
+ hasSources() {
1928
+ return this.sources.size > 0;
1929
+ }
1930
+ };
1931
+
1932
+ // src/storage/errors.ts
1933
+ var FilePluginFileNotFoundError = class _FilePluginFileNotFoundError extends Error {
1934
+ constructor(filePath, sourceName, options) {
1935
+ const includeCloudSuggestions = options?.includeCloudSuggestions !== false;
1936
+ const storageType = options?.storageType;
1937
+ let message = `File not found: ${filePath}
1938
+
1939
+ `;
1940
+ if (includeCloudSuggestions) {
1941
+ if (storageType) {
1942
+ message += `This file may be in cloud storage (${storageType}).
1943
+
1944
+ `;
1945
+ } else {
1946
+ message += `This file may not have been synced from remote storage yet.
1947
+ `;
1948
+ }
1949
+ message += `To fetch from cloud storage, run:
1950
+ `;
1951
+ message += ` file pull ${sourceName}
1952
+
1953
+ `;
1954
+ message += `Or sync all sources:
1955
+ `;
1956
+ message += ` file sync`;
1957
+ } else {
1958
+ message += `Please ensure the file exists locally or pull it from cloud storage.`;
1959
+ }
1960
+ super(message);
1961
+ this.filePath = filePath;
1962
+ this.sourceName = sourceName;
1963
+ this.name = "FilePluginFileNotFoundError";
1964
+ if (Error.captureStackTrace) {
1965
+ Error.captureStackTrace(this, _FilePluginFileNotFoundError);
1966
+ }
1967
+ }
1968
+ };
1969
+
1970
+ // src/storage/file-plugin.ts
1971
+ var FilePluginStorage = class {
1972
+ constructor(options) {
1973
+ this.options = options;
1974
+ this.sourceResolver = new FileSourceResolver(options.config);
1975
+ this.baseDir = options.baseDir || process.cwd();
1976
+ }
1977
+ name = "file-plugin";
1978
+ type = "local";
1979
+ // Reuse local type for compatibility
1980
+ sourceResolver;
1981
+ baseDir;
1982
+ /**
1983
+ * Check if this provider can handle the reference
1984
+ *
1985
+ * Only handles:
1986
+ * - Current project references
1987
+ * - With sourceType === 'file-plugin'
1988
+ *
1989
+ * @param reference - Resolved reference
1990
+ * @returns True if this provider can handle the reference
1991
+ */
1992
+ canHandle(reference) {
1993
+ return reference.isCurrentProject && reference.sourceType === "file-plugin";
1994
+ }
1995
+ /**
1996
+ * Fetch content for a reference
1997
+ *
1998
+ * Reads from local filesystem based on the resolved local path.
1999
+ * If file not found and S3 fallback is enabled, throws helpful error.
2000
+ *
2001
+ * @param reference - Resolved reference
2002
+ * @param options - Fetch options (unused for local reads)
2003
+ * @returns Fetch result with content
2004
+ */
2005
+ async fetch(reference, _options) {
2006
+ if (!reference.localPath) {
2007
+ throw new Error(`File plugin reference missing localPath: ${reference.uri}`);
2008
+ }
2009
+ if (!reference.filePluginSource) {
2010
+ throw new Error(`File plugin reference missing source name: ${reference.uri}`);
2011
+ }
2012
+ const absolutePath = path3__namespace.isAbsolute(reference.localPath) ? path3__namespace.resolve(reference.localPath) : path3__namespace.resolve(this.baseDir, reference.localPath);
2013
+ const source = this.sourceResolver.resolveSource(reference.filePluginSource);
2014
+ if (source) {
2015
+ const allowedDir = path3__namespace.resolve(this.baseDir, source.localPath);
2016
+ if (!absolutePath.startsWith(allowedDir + path3__namespace.sep) && absolutePath !== allowedDir) {
2017
+ throw new Error(
2018
+ `Path traversal detected: ${reference.localPath} resolves outside allowed directory ${source.localPath}`
2019
+ );
2020
+ }
2021
+ }
2022
+ try {
2023
+ const content = await fs3__namespace.readFile(absolutePath);
2024
+ const contentType = this.detectContentType(absolutePath);
2025
+ return {
2026
+ content,
2027
+ contentType,
2028
+ size: content.length,
2029
+ source: "file-plugin",
2030
+ metadata: {
2031
+ filePluginSource: reference.filePluginSource,
2032
+ localPath: absolutePath
2033
+ }
2034
+ };
2035
+ } catch (error) {
2036
+ if (error.code === "ENOENT") {
2037
+ const source2 = this.sourceResolver.resolveSource(reference.filePluginSource);
2038
+ throw this.createFileNotFoundError(reference, source2);
2039
+ }
2040
+ throw error;
2041
+ }
2042
+ }
2043
+ /**
2044
+ * Check if a reference exists
2045
+ *
2046
+ * @param reference - Resolved reference
2047
+ * @param options - Fetch options (unused)
2048
+ * @returns True if file exists
2049
+ */
2050
+ async exists(reference, _options) {
2051
+ if (!reference.localPath) {
2052
+ return false;
2053
+ }
2054
+ const absolutePath = path3__namespace.isAbsolute(reference.localPath) ? path3__namespace.resolve(reference.localPath) : path3__namespace.resolve(this.baseDir, reference.localPath);
2055
+ if (reference.filePluginSource) {
2056
+ const source = this.sourceResolver.resolveSource(reference.filePluginSource);
2057
+ if (source) {
2058
+ const allowedDir = path3__namespace.resolve(this.baseDir, source.localPath);
2059
+ if (!absolutePath.startsWith(allowedDir + path3__namespace.sep) && absolutePath !== allowedDir) {
2060
+ return false;
2061
+ }
2062
+ }
2063
+ }
2064
+ try {
2065
+ await fs3__namespace.access(absolutePath);
2066
+ return true;
2067
+ } catch {
2068
+ return false;
2069
+ }
2070
+ }
2071
+ /**
2072
+ * Detect content type from file extension
2073
+ */
2074
+ detectContentType(filePath) {
2075
+ const ext = path3__namespace.extname(filePath).toLowerCase();
2076
+ const mimeTypes = {
2077
+ ".md": "text/markdown",
2078
+ ".txt": "text/plain",
2079
+ ".json": "application/json",
2080
+ ".yaml": "text/yaml",
2081
+ ".yml": "text/yaml",
2082
+ ".html": "text/html",
2083
+ ".xml": "application/xml",
2084
+ ".log": "text/plain",
2085
+ ".js": "application/javascript",
2086
+ ".ts": "application/typescript",
2087
+ ".py": "text/x-python",
2088
+ ".sh": "application/x-sh"
2089
+ };
2090
+ return mimeTypes[ext] || "application/octet-stream";
2091
+ }
2092
+ /**
2093
+ * Create a helpful error message when file is not found
2094
+ */
2095
+ createFileNotFoundError(reference, source) {
2096
+ const includeCloudSuggestions = this.options.enableS3Fallback !== false;
2097
+ const storageType = source?.config.type;
2098
+ return new FilePluginFileNotFoundError(
2099
+ reference.localPath || reference.path || "",
2100
+ reference.filePluginSource || "",
2101
+ {
2102
+ includeCloudSuggestions,
2103
+ storageType
2104
+ }
2105
+ );
2106
+ }
2107
+ };
1725
2108
  var execFileAsync = util.promisify(child_process.execFile);
1726
2109
  async function execFileNoThrow(command, args = [], options) {
1727
2110
  try {
@@ -1871,10 +2254,10 @@ var S3ArchiveStorage = class {
1871
2254
  *
1872
2255
  * Used to organize archives by type
1873
2256
  */
1874
- detectType(path6) {
1875
- if (path6.startsWith("specs/")) return "specs";
1876
- if (path6.startsWith("docs/")) return "docs";
1877
- if (path6.includes("/logs/")) return "logs";
2257
+ detectType(path7) {
2258
+ if (path7.startsWith("specs/")) return "specs";
2259
+ if (path7.startsWith("docs/")) return "docs";
2260
+ if (path7.includes("/logs/")) return "logs";
1878
2261
  return "misc";
1879
2262
  }
1880
2263
  /**
@@ -1885,9 +2268,9 @@ var S3ArchiveStorage = class {
1885
2268
  * - *.md (all markdown files)
1886
2269
  * - docs/*.md (markdown files in docs/)
1887
2270
  */
1888
- matchesPatterns(path6, patterns) {
2271
+ matchesPatterns(path7, patterns) {
1889
2272
  for (const pattern of patterns) {
1890
- if (this.matchesPattern(path6, pattern)) {
2273
+ if (this.matchesPattern(path7, pattern)) {
1891
2274
  return true;
1892
2275
  }
1893
2276
  }
@@ -1896,14 +2279,14 @@ var S3ArchiveStorage = class {
1896
2279
  /**
1897
2280
  * Check if path matches a single pattern
1898
2281
  */
1899
- matchesPattern(path6, pattern) {
2282
+ matchesPattern(path7, pattern) {
1900
2283
  const DOUBLE_STAR = "\0DOUBLE_STAR\0";
1901
2284
  let regexPattern = pattern.replace(/\*\*/g, DOUBLE_STAR);
1902
2285
  regexPattern = regexPattern.replace(/[.[\](){}+^$|\\]/g, "\\$&");
1903
2286
  regexPattern = regexPattern.replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
1904
2287
  regexPattern = regexPattern.replace(new RegExp(DOUBLE_STAR, "g"), ".*");
1905
2288
  const regex = new RegExp(`^${regexPattern}$`);
1906
- return regex.test(path6);
2289
+ return regex.test(path7);
1907
2290
  }
1908
2291
  };
1909
2292
 
@@ -1918,7 +2301,10 @@ var StorageManager = class {
1918
2301
  if (config.s3Archive) {
1919
2302
  this.providers.set("s3-archive", new S3ArchiveStorage(config.s3Archive));
1920
2303
  }
1921
- this.priority = config.priority || (config.s3Archive ? ["local", "s3-archive", "github", "http"] : ["local", "github", "http"]);
2304
+ if (config.filePlugin) {
2305
+ this.providers.set("file-plugin", new FilePluginStorage(config.filePlugin));
2306
+ }
2307
+ this.priority = config.priority || (config.filePlugin && config.s3Archive ? ["file-plugin", "local", "s3-archive", "github", "http"] : config.filePlugin ? ["file-plugin", "local", "github", "http"] : config.s3Archive ? ["local", "s3-archive", "github", "http"] : ["local", "github", "http"]);
1922
2308
  }
1923
2309
  /**
1924
2310
  * Register a custom storage provider
@@ -2158,7 +2544,7 @@ var CachePersistence = class {
2158
2544
  }
2159
2545
  const [, org, project, filePath] = match;
2160
2546
  const relativePath = filePath || "index";
2161
- return path3__default.default.join(this.cacheDir, org, project, relativePath + this.extension);
2547
+ return path3__namespace.default.join(this.cacheDir, org, project, relativePath + this.extension);
2162
2548
  }
2163
2549
  /**
2164
2550
  * Get the metadata file path for a URI
@@ -2174,8 +2560,8 @@ var CachePersistence = class {
2174
2560
  const metadataPath = this.getMetadataPath(uri);
2175
2561
  try {
2176
2562
  const [metadataJson, content] = await Promise.all([
2177
- fs2__default.default.readFile(metadataPath, "utf-8"),
2178
- fs2__default.default.readFile(cachePath)
2563
+ fs3__namespace.default.readFile(metadataPath, "utf-8"),
2564
+ fs3__namespace.default.readFile(cachePath)
2179
2565
  ]);
2180
2566
  const metadata = JSON.parse(metadataJson);
2181
2567
  return {
@@ -2195,32 +2581,32 @@ var CachePersistence = class {
2195
2581
  async write(entry) {
2196
2582
  const cachePath = this.getCachePath(entry.metadata.uri);
2197
2583
  const metadataPath = this.getMetadataPath(entry.metadata.uri);
2198
- await fs2__default.default.mkdir(path3__default.default.dirname(cachePath), { recursive: true });
2584
+ await fs3__namespace.default.mkdir(path3__namespace.default.dirname(cachePath), { recursive: true });
2199
2585
  if (this.atomicWrites) {
2200
2586
  const tempCachePath = cachePath + ".tmp";
2201
2587
  const tempMetadataPath = metadataPath + ".tmp";
2202
2588
  try {
2203
2589
  await Promise.all([
2204
- fs2__default.default.writeFile(tempCachePath, entry.content),
2205
- fs2__default.default.writeFile(tempMetadataPath, JSON.stringify(entry.metadata, null, 2))
2590
+ fs3__namespace.default.writeFile(tempCachePath, entry.content),
2591
+ fs3__namespace.default.writeFile(tempMetadataPath, JSON.stringify(entry.metadata, null, 2))
2206
2592
  ]);
2207
2593
  await Promise.all([
2208
- fs2__default.default.rename(tempCachePath, cachePath),
2209
- fs2__default.default.rename(tempMetadataPath, metadataPath)
2594
+ fs3__namespace.default.rename(tempCachePath, cachePath),
2595
+ fs3__namespace.default.rename(tempMetadataPath, metadataPath)
2210
2596
  ]);
2211
2597
  } catch (error) {
2212
2598
  await Promise.all([
2213
- fs2__default.default.unlink(tempCachePath).catch(() => {
2599
+ fs3__namespace.default.unlink(tempCachePath).catch(() => {
2214
2600
  }),
2215
- fs2__default.default.unlink(tempMetadataPath).catch(() => {
2601
+ fs3__namespace.default.unlink(tempMetadataPath).catch(() => {
2216
2602
  })
2217
2603
  ]);
2218
2604
  throw error;
2219
2605
  }
2220
2606
  } else {
2221
2607
  await Promise.all([
2222
- fs2__default.default.writeFile(cachePath, entry.content),
2223
- fs2__default.default.writeFile(metadataPath, JSON.stringify(entry.metadata, null, 2))
2608
+ fs3__namespace.default.writeFile(cachePath, entry.content),
2609
+ fs3__namespace.default.writeFile(metadataPath, JSON.stringify(entry.metadata, null, 2))
2224
2610
  ]);
2225
2611
  }
2226
2612
  }
@@ -2231,7 +2617,7 @@ var CachePersistence = class {
2231
2617
  const cachePath = this.getCachePath(uri);
2232
2618
  const metadataPath = this.getMetadataPath(uri);
2233
2619
  try {
2234
- await Promise.all([fs2__default.default.unlink(cachePath), fs2__default.default.unlink(metadataPath)]);
2620
+ await Promise.all([fs3__namespace.default.unlink(cachePath), fs3__namespace.default.unlink(metadataPath)]);
2235
2621
  return true;
2236
2622
  } catch (error) {
2237
2623
  if (error.code === "ENOENT") {
@@ -2246,7 +2632,7 @@ var CachePersistence = class {
2246
2632
  async exists(uri) {
2247
2633
  const cachePath = this.getCachePath(uri);
2248
2634
  try {
2249
- await fs2__default.default.access(cachePath);
2635
+ await fs3__namespace.default.access(cachePath);
2250
2636
  return true;
2251
2637
  } catch {
2252
2638
  return false;
@@ -2258,20 +2644,20 @@ var CachePersistence = class {
2258
2644
  async list() {
2259
2645
  const uris = [];
2260
2646
  try {
2261
- const orgs = await fs2__default.default.readdir(this.cacheDir);
2647
+ const orgs = await fs3__namespace.default.readdir(this.cacheDir);
2262
2648
  for (const org of orgs) {
2263
- const orgPath = path3__default.default.join(this.cacheDir, org);
2264
- const orgStat = await fs2__default.default.stat(orgPath);
2649
+ const orgPath = path3__namespace.default.join(this.cacheDir, org);
2650
+ const orgStat = await fs3__namespace.default.stat(orgPath);
2265
2651
  if (!orgStat.isDirectory()) continue;
2266
- const projects = await fs2__default.default.readdir(orgPath);
2652
+ const projects = await fs3__namespace.default.readdir(orgPath);
2267
2653
  for (const project of projects) {
2268
- const projectPath = path3__default.default.join(orgPath, project);
2269
- const projectStat = await fs2__default.default.stat(projectPath);
2654
+ const projectPath = path3__namespace.default.join(orgPath, project);
2655
+ const projectStat = await fs3__namespace.default.stat(projectPath);
2270
2656
  if (!projectStat.isDirectory()) continue;
2271
2657
  const files = await this.listFilesRecursive(projectPath);
2272
2658
  for (const file of files) {
2273
2659
  if (file.endsWith(this.extension)) {
2274
- const relativePath = path3__default.default.relative(projectPath, file);
2660
+ const relativePath = path3__namespace.default.relative(projectPath, file);
2275
2661
  const filePath = relativePath.slice(0, -this.extension.length);
2276
2662
  uris.push(`codex://${org}/${project}/${filePath}`);
2277
2663
  }
@@ -2291,9 +2677,9 @@ var CachePersistence = class {
2291
2677
  */
2292
2678
  async listFilesRecursive(dir) {
2293
2679
  const files = [];
2294
- const entries = await fs2__default.default.readdir(dir, { withFileTypes: true });
2680
+ const entries = await fs3__namespace.default.readdir(dir, { withFileTypes: true });
2295
2681
  for (const entry of entries) {
2296
- const fullPath = path3__default.default.join(dir, entry.name);
2682
+ const fullPath = path3__namespace.default.join(dir, entry.name);
2297
2683
  if (entry.isDirectory()) {
2298
2684
  files.push(...await this.listFilesRecursive(fullPath));
2299
2685
  } else if (entry.isFile()) {
@@ -2308,12 +2694,12 @@ var CachePersistence = class {
2308
2694
  async clear() {
2309
2695
  let count = 0;
2310
2696
  try {
2311
- const orgs = await fs2__default.default.readdir(this.cacheDir);
2697
+ const orgs = await fs3__namespace.default.readdir(this.cacheDir);
2312
2698
  for (const org of orgs) {
2313
- const orgPath = path3__default.default.join(this.cacheDir, org);
2314
- const stat = await fs2__default.default.stat(orgPath);
2699
+ const orgPath = path3__namespace.default.join(this.cacheDir, org);
2700
+ const stat = await fs3__namespace.default.stat(orgPath);
2315
2701
  if (stat.isDirectory()) {
2316
- await fs2__default.default.rm(orgPath, { recursive: true });
2702
+ await fs3__namespace.default.rm(orgPath, { recursive: true });
2317
2703
  count++;
2318
2704
  }
2319
2705
  }
@@ -2374,7 +2760,7 @@ var CachePersistence = class {
2374
2760
  * Ensure cache directory exists
2375
2761
  */
2376
2762
  async ensureDir() {
2377
- await fs2__default.default.mkdir(this.cacheDir, { recursive: true });
2763
+ await fs3__namespace.default.mkdir(this.cacheDir, { recursive: true });
2378
2764
  }
2379
2765
  /**
2380
2766
  * Get cache directory path
@@ -2422,8 +2808,17 @@ var CacheManager = class {
2422
2808
  * Get content for a reference
2423
2809
  *
2424
2810
  * Implements cache-first strategy with stale-while-revalidate.
2811
+ *
2812
+ * EXCEPTION: File plugin sources (current project files) bypass cache entirely.
2813
+ * They are always read fresh from disk for optimal development experience.
2425
2814
  */
2426
2815
  async get(reference, options) {
2816
+ if (reference.isCurrentProject && reference.sourceType === "file-plugin") {
2817
+ if (!this.storage) {
2818
+ throw new Error("Storage manager not set");
2819
+ }
2820
+ return await this.storage.fetch(reference, options);
2821
+ }
2427
2822
  const ttl = options?.ttl ?? this.config.defaultTtl;
2428
2823
  let entry = this.memoryCache.get(reference.uri);
2429
2824
  if (!entry && this.persistence) {
@@ -2629,13 +3024,18 @@ var CacheManager = class {
2629
3024
  }
2630
3025
  /**
2631
3026
  * Fetch content and store in cache
3027
+ *
3028
+ * EXCEPTION: File plugin sources are not cached (should not reach here,
3029
+ * but added as safety check).
2632
3030
  */
2633
3031
  async fetchAndCache(reference, ttl, options) {
2634
3032
  if (!this.storage) {
2635
3033
  throw new Error("Storage manager not set");
2636
3034
  }
2637
3035
  const result = await this.storage.fetch(reference, options);
2638
- await this.set(reference.uri, result, ttl);
3036
+ if (!(reference.isCurrentProject && reference.sourceType === "file-plugin")) {
3037
+ await this.set(reference.uri, result, ttl);
3038
+ }
2639
3039
  return result;
2640
3040
  }
2641
3041
  /**
@@ -2754,11 +3154,11 @@ var DEFAULT_SYNC_CONFIG = {
2754
3154
  deleteOrphans: false,
2755
3155
  conflictStrategy: "newest"
2756
3156
  };
2757
- function evaluatePath(path6, rules, direction, defaultExcludes = []) {
3157
+ function evaluatePath(path7, rules, direction, defaultExcludes = []) {
2758
3158
  for (const pattern of defaultExcludes) {
2759
- if (micromatch3__default.default.isMatch(path6, pattern)) {
3159
+ if (micromatch3__default.default.isMatch(path7, pattern)) {
2760
3160
  return {
2761
- path: path6,
3161
+ path: path7,
2762
3162
  shouldSync: false,
2763
3163
  reason: `Excluded by default pattern: ${pattern}`
2764
3164
  };
@@ -2769,9 +3169,9 @@ function evaluatePath(path6, rules, direction, defaultExcludes = []) {
2769
3169
  if (rule.direction && rule.direction !== direction) {
2770
3170
  continue;
2771
3171
  }
2772
- if (micromatch3__default.default.isMatch(path6, rule.pattern)) {
3172
+ if (micromatch3__default.default.isMatch(path7, rule.pattern)) {
2773
3173
  return {
2774
- path: path6,
3174
+ path: path7,
2775
3175
  shouldSync: rule.include,
2776
3176
  matchedRule: rule,
2777
3177
  reason: rule.include ? `Included by rule: ${rule.pattern}` : `Excluded by rule: ${rule.pattern}`
@@ -2779,21 +3179,21 @@ function evaluatePath(path6, rules, direction, defaultExcludes = []) {
2779
3179
  }
2780
3180
  }
2781
3181
  return {
2782
- path: path6,
3182
+ path: path7,
2783
3183
  shouldSync: true,
2784
3184
  reason: "No matching rule, included by default"
2785
3185
  };
2786
3186
  }
2787
3187
  function evaluatePaths(paths, rules, direction, defaultExcludes = []) {
2788
3188
  const results = /* @__PURE__ */ new Map();
2789
- for (const path6 of paths) {
2790
- results.set(path6, evaluatePath(path6, rules, direction, defaultExcludes));
3189
+ for (const path7 of paths) {
3190
+ results.set(path7, evaluatePath(path7, rules, direction, defaultExcludes));
2791
3191
  }
2792
3192
  return results;
2793
3193
  }
2794
3194
  function filterSyncablePaths(paths, rules, direction, defaultExcludes = []) {
2795
3195
  return paths.filter(
2796
- (path6) => evaluatePath(path6, rules, direction, defaultExcludes).shouldSync
3196
+ (path7) => evaluatePath(path7, rules, direction, defaultExcludes).shouldSync
2797
3197
  );
2798
3198
  }
2799
3199
  function createRulesFromPatterns(include = [], exclude = []) {
@@ -3059,8 +3459,8 @@ async function scanCodexWithRouting(options) {
3059
3459
  for (const filePath of allFiles) {
3060
3460
  totalScanned++;
3061
3461
  try {
3062
- const fullPath = path3__default.default.join(codexDir, filePath);
3063
- const stats = await fs2__default.default.stat(fullPath);
3462
+ const fullPath = path3__namespace.default.join(codexDir, filePath);
3463
+ const stats = await fs3__namespace.default.stat(fullPath);
3064
3464
  if (stats.size > maxFileSize) {
3065
3465
  totalSkipped++;
3066
3466
  errors.push({
@@ -3156,10 +3556,10 @@ async function listAllFilesRecursive(dirPath) {
3156
3556
  const files = [];
3157
3557
  async function scanDirectory(currentPath, relativePath = "") {
3158
3558
  try {
3159
- const entries = await fs2__default.default.readdir(currentPath, { withFileTypes: true });
3559
+ const entries = await fs3__namespace.default.readdir(currentPath, { withFileTypes: true });
3160
3560
  for (const entry of entries) {
3161
- const entryPath = path3__default.default.join(currentPath, entry.name);
3162
- const entryRelativePath = relativePath ? path3__default.default.join(relativePath, entry.name) : entry.name;
3561
+ const entryPath = path3__namespace.default.join(currentPath, entry.name);
3562
+ const entryRelativePath = relativePath ? path3__namespace.default.join(relativePath, entry.name) : entry.name;
3163
3563
  if (entry.isDirectory()) {
3164
3564
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build") {
3165
3565
  continue;
@@ -3398,17 +3798,17 @@ var SyncManager = class {
3398
3798
  if (file.operation === "create" || file.operation === "update") {
3399
3799
  const sourcePath = `${plan.source}/${file.path}`;
3400
3800
  const targetPath = `${plan.target}/${file.path}`;
3401
- const fs4 = await import('fs/promises');
3402
- const path6 = await import('path');
3403
- const targetDir = path6.dirname(targetPath);
3404
- await fs4.mkdir(targetDir, { recursive: true });
3405
- await fs4.copyFile(sourcePath, targetPath);
3801
+ const fs5 = await import('fs/promises');
3802
+ const path7 = await import('path');
3803
+ const targetDir = path7.dirname(targetPath);
3804
+ await fs5.mkdir(targetDir, { recursive: true });
3805
+ await fs5.copyFile(sourcePath, targetPath);
3406
3806
  synced++;
3407
3807
  } else if (file.operation === "delete") {
3408
3808
  const targetPath = `${plan.target}/${file.path}`;
3409
- const fs4 = await import('fs/promises');
3809
+ const fs5 = await import('fs/promises');
3410
3810
  try {
3411
- await fs4.unlink(targetPath);
3811
+ await fs5.unlink(targetPath);
3412
3812
  synced++;
3413
3813
  } catch (error) {
3414
3814
  if (error.code !== "ENOENT") {
@@ -3463,15 +3863,15 @@ var SyncManager = class {
3463
3863
  /**
3464
3864
  * Get sync status for a file
3465
3865
  */
3466
- async getFileStatus(path6) {
3866
+ async getFileStatus(path7) {
3467
3867
  const manifest = await this.loadManifest();
3468
- return manifest?.entries[path6] ?? null;
3868
+ return manifest?.entries[path7] ?? null;
3469
3869
  }
3470
3870
  /**
3471
3871
  * Check if a file is synced
3472
3872
  */
3473
- async isFileSynced(path6) {
3474
- const status = await this.getFileStatus(path6);
3873
+ async isFileSynced(path7) {
3874
+ const status = await this.getFileStatus(path7);
3475
3875
  return status !== null;
3476
3876
  }
3477
3877
  /**
@@ -3555,19 +3955,19 @@ function ruleMatchesContext(rule, context) {
3555
3955
  }
3556
3956
  return true;
3557
3957
  }
3558
- function ruleMatchesPath(rule, path6) {
3559
- return micromatch3__default.default.isMatch(path6, rule.pattern);
3958
+ function ruleMatchesPath(rule, path7) {
3959
+ return micromatch3__default.default.isMatch(path7, rule.pattern);
3560
3960
  }
3561
3961
  function ruleMatchesAction(rule, action) {
3562
3962
  return rule.actions.includes(action);
3563
3963
  }
3564
- function evaluatePermission(path6, action, context, config) {
3964
+ function evaluatePermission(path7, action, context, config) {
3565
3965
  const sortedRules = [...config.rules].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
3566
3966
  for (const rule of sortedRules) {
3567
3967
  if (!ruleMatchesContext(rule, context)) {
3568
3968
  continue;
3569
3969
  }
3570
- if (!ruleMatchesPath(rule, path6)) {
3970
+ if (!ruleMatchesPath(rule, path7)) {
3571
3971
  continue;
3572
3972
  }
3573
3973
  if (!ruleMatchesAction(rule, action)) {
@@ -3586,24 +3986,24 @@ function evaluatePermission(path6, action, context, config) {
3586
3986
  reason: config.defaultAllow ? "Allowed by default" : "Denied by default"
3587
3987
  };
3588
3988
  }
3589
- function isAllowed(path6, action, context, config) {
3590
- const result = evaluatePermission(path6, action, context, config);
3989
+ function isAllowed(path7, action, context, config) {
3990
+ const result = evaluatePermission(path7, action, context, config);
3591
3991
  return result.allowed;
3592
3992
  }
3593
- function hasPermission(path6, action, requiredLevel, context, config) {
3594
- const result = evaluatePermission(path6, action, context, config);
3993
+ function hasPermission(path7, action, requiredLevel, context, config) {
3994
+ const result = evaluatePermission(path7, action, context, config);
3595
3995
  return levelGrants(result.level, requiredLevel);
3596
3996
  }
3597
3997
  function evaluatePermissions(paths, action, context, config) {
3598
3998
  const results = /* @__PURE__ */ new Map();
3599
- for (const path6 of paths) {
3600
- results.set(path6, evaluatePermission(path6, action, context, config));
3999
+ for (const path7 of paths) {
4000
+ results.set(path7, evaluatePermission(path7, action, context, config));
3601
4001
  }
3602
4002
  return results;
3603
4003
  }
3604
4004
  function filterByPermission(paths, action, context, config, requiredLevel = "read") {
3605
- return paths.filter((path6) => {
3606
- const result = evaluatePermission(path6, action, context, config);
4005
+ return paths.filter((path7) => {
4006
+ const result = evaluatePermission(path7, action, context, config);
3607
4007
  return levelGrants(result.level, requiredLevel);
3608
4008
  });
3609
4009
  }
@@ -3694,21 +4094,21 @@ var PermissionManager = class {
3694
4094
  /**
3695
4095
  * Check if an action is allowed for a path
3696
4096
  */
3697
- isAllowed(path6, action, context) {
3698
- const result = this.evaluate(path6, action, context);
4097
+ isAllowed(path7, action, context) {
4098
+ const result = this.evaluate(path7, action, context);
3699
4099
  return result.allowed;
3700
4100
  }
3701
4101
  /**
3702
4102
  * Check if a permission level is granted
3703
4103
  */
3704
- hasPermission(path6, action, requiredLevel, context) {
3705
- const result = this.evaluate(path6, action, context);
4104
+ hasPermission(path7, action, requiredLevel, context) {
4105
+ const result = this.evaluate(path7, action, context);
3706
4106
  return levelGrants(result.level, requiredLevel);
3707
4107
  }
3708
4108
  /**
3709
4109
  * Evaluate permission for a path and action
3710
4110
  */
3711
- evaluate(path6, action, context) {
4111
+ evaluate(path7, action, context) {
3712
4112
  const mergedContext = { ...this.defaultContext, ...context };
3713
4113
  if (!this.config.enforced) {
3714
4114
  return {
@@ -3717,7 +4117,7 @@ var PermissionManager = class {
3717
4117
  reason: "Permissions not enforced"
3718
4118
  };
3719
4119
  }
3720
- return evaluatePermission(path6, action, mergedContext, this.config);
4120
+ return evaluatePermission(path7, action, mergedContext, this.config);
3721
4121
  }
3722
4122
  /**
3723
4123
  * Filter paths by permission
@@ -3820,12 +4220,12 @@ var PermissionManager = class {
3820
4220
  /**
3821
4221
  * Assert permission (throws if denied)
3822
4222
  */
3823
- assertPermission(path6, action, requiredLevel = "read", context) {
3824
- const result = this.evaluate(path6, action, context);
4223
+ assertPermission(path7, action, requiredLevel = "read", context) {
4224
+ const result = this.evaluate(path7, action, context);
3825
4225
  if (!levelGrants(result.level, requiredLevel)) {
3826
4226
  throw new PermissionDeniedError(
3827
- `Permission denied for ${action} on ${path6}: ${result.reason}`,
3828
- path6,
4227
+ `Permission denied for ${action} on ${path7}: ${result.reason}`,
4228
+ path7,
3829
4229
  action,
3830
4230
  result
3831
4231
  );
@@ -3833,9 +4233,9 @@ var PermissionManager = class {
3833
4233
  }
3834
4234
  };
3835
4235
  var PermissionDeniedError = class extends Error {
3836
- constructor(message, path6, action, result) {
4236
+ constructor(message, path7, action, result) {
3837
4237
  super(message);
3838
- this.path = path6;
4238
+ this.path = path7;
3839
4239
  this.action = action;
3840
4240
  this.result = result;
3841
4241
  this.name = "PermissionDeniedError";
@@ -4332,8 +4732,8 @@ function convertToUri(reference, options = {}) {
4332
4732
  const parts = parseReference2(trimmed);
4333
4733
  const org = parts.org || options.defaultOrg || "_";
4334
4734
  const project = parts.project || options.defaultProject || "_";
4335
- const path6 = parts.path;
4336
- return `codex://${org}/${project}/${path6}`;
4735
+ const path7 = parts.path;
4736
+ return `codex://${org}/${project}/${path7}`;
4337
4737
  }
4338
4738
  function parseReference2(reference) {
4339
4739
  const trimmed = reference.trim();
@@ -4429,6 +4829,8 @@ exports.DEFAULT_MIGRATION_OPTIONS = DEFAULT_MIGRATION_OPTIONS;
4429
4829
  exports.DEFAULT_PERMISSION_CONFIG = DEFAULT_PERMISSION_CONFIG;
4430
4830
  exports.DEFAULT_SYNC_CONFIG = DEFAULT_SYNC_CONFIG;
4431
4831
  exports.DEFAULT_TYPE = DEFAULT_TYPE;
4832
+ exports.FilePluginFileNotFoundError = FilePluginFileNotFoundError;
4833
+ exports.FilePluginStorage = FilePluginStorage;
4432
4834
  exports.GitHubStorage = GitHubStorage;
4433
4835
  exports.HttpStorage = HttpStorage;
4434
4836
  exports.LEGACY_PATTERNS = LEGACY_PATTERNS;