@fractary/codex-mcp 0.8.0 → 0.9.1

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/cli.cjs CHANGED
@@ -5586,6 +5586,7 @@ var {
5586
5586
  // ../../sdk/js/dist/index.js
5587
5587
  init_cjs_shims();
5588
5588
  var import_micromatch = __toESM(require_micromatch(), 1);
5589
+ var path3 = __toESM(require("path"), 1);
5589
5590
  var import_path = __toESM(require("path"), 1);
5590
5591
  var import_child_process = require("child_process");
5591
5592
 
@@ -12237,6 +12238,7 @@ var safeLoadAll = renamed("safeLoadAll", "loadAll");
12237
12238
  var safeDump = renamed("safeDump", "dump");
12238
12239
 
12239
12240
  // ../../sdk/js/dist/index.js
12241
+ var fs3 = __toESM(require("fs/promises"), 1);
12240
12242
  var import_promises = __toESM(require("fs/promises"), 1);
12241
12243
  var import_util4 = require("util");
12242
12244
  var __defProp2 = Object.defineProperty;
@@ -12488,9 +12490,45 @@ function resolveReference(uri, options = {}) {
12488
12490
  };
12489
12491
  if (isCurrentProject && parsed.path) {
12490
12492
  resolved.localPath = parsed.path;
12493
+ if (options.config) {
12494
+ const fileSource = detectFilePluginSource(parsed.path, options.config);
12495
+ if (fileSource) {
12496
+ resolved.sourceType = "file-plugin";
12497
+ resolved.filePluginSource = fileSource.name;
12498
+ resolved.localPath = fileSource.fullPath;
12499
+ }
12500
+ }
12491
12501
  }
12492
12502
  return resolved;
12493
12503
  }
12504
+ function detectFilePluginSource(filePath, config) {
12505
+ if (!config?.file?.sources) {
12506
+ return null;
12507
+ }
12508
+ for (const [sourceName, source] of Object.entries(config.file.sources)) {
12509
+ const basePath = source.local?.base_path;
12510
+ if (!basePath) {
12511
+ continue;
12512
+ }
12513
+ const normalizedPath = filePath.replace(/^\.\//, "").replace(/^\//, "");
12514
+ const normalizedBasePath = basePath.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "");
12515
+ if (normalizedPath.startsWith(normalizedBasePath)) {
12516
+ return {
12517
+ name: sourceName,
12518
+ fullPath: import_path.default.join(basePath, normalizedPath.substring(normalizedBasePath.length).replace(/^\//, ""))
12519
+ };
12520
+ }
12521
+ const sourceNameInPath = normalizedBasePath.split("/").pop();
12522
+ if (sourceNameInPath && normalizedPath.startsWith(sourceNameInPath + "/")) {
12523
+ const pathWithoutSource = normalizedPath.substring(sourceNameInPath.length + 1);
12524
+ return {
12525
+ name: sourceName,
12526
+ fullPath: import_path.default.join(basePath, pathWithoutSource)
12527
+ };
12528
+ }
12529
+ }
12530
+ return null;
12531
+ }
12494
12532
  var TTL = {
12495
12533
  ONE_HOUR: 3600,
12496
12534
  ONE_DAY: 86400,
@@ -12649,8 +12687,40 @@ var ArchiveProjectConfigSchema = external_exports.object({
12649
12687
  var ArchiveConfigSchema = external_exports.object({
12650
12688
  projects: external_exports.record(ArchiveProjectConfigSchema)
12651
12689
  });
12690
+ var GitHubAuthConfigSchema = external_exports.object({
12691
+ /** Default token environment variable name (default: GITHUB_TOKEN) */
12692
+ default_token_env: external_exports.string().optional(),
12693
+ /** Fallback to public access if authentication fails */
12694
+ fallback_to_public: external_exports.boolean().optional()
12695
+ });
12696
+ var AuthConfigSchema = external_exports.object({
12697
+ /** GitHub authentication configuration */
12698
+ github: GitHubAuthConfigSchema.optional()
12699
+ });
12700
+ var SourceConfigSchema = external_exports.object({
12701
+ /** Source type */
12702
+ type: external_exports.enum(["github", "s3", "http", "local"]),
12703
+ /** Environment variable containing the authentication token */
12704
+ token_env: external_exports.string().optional(),
12705
+ /** Direct token value (not recommended, use token_env instead) */
12706
+ token: external_exports.string().optional(),
12707
+ /** Branch to fetch from (for GitHub sources) */
12708
+ branch: external_exports.string().optional(),
12709
+ /** Base URL (for HTTP sources) */
12710
+ base_url: external_exports.string().optional(),
12711
+ /** Bucket name (for S3 sources) */
12712
+ bucket: external_exports.string().optional(),
12713
+ /** Prefix/path within bucket (for S3 sources) */
12714
+ prefix: external_exports.string().optional()
12715
+ });
12716
+ var DependencyConfigSchema = external_exports.object({
12717
+ /** Sources within this dependency */
12718
+ sources: external_exports.record(SourceConfigSchema)
12719
+ });
12652
12720
  var CodexConfigSchema = external_exports.object({
12653
12721
  organizationSlug: external_exports.string(),
12722
+ /** Project name (optional) */
12723
+ project: external_exports.string().optional(),
12654
12724
  directories: external_exports.object({
12655
12725
  source: external_exports.string().optional(),
12656
12726
  target: external_exports.string().optional(),
@@ -12660,8 +12730,47 @@ var CodexConfigSchema = external_exports.object({
12660
12730
  // Directional sync configuration
12661
12731
  sync: DirectionalSyncSchema.optional(),
12662
12732
  // Archive configuration
12663
- archive: ArchiveConfigSchema.optional()
12733
+ archive: ArchiveConfigSchema.optional(),
12734
+ // Authentication configuration
12735
+ auth: AuthConfigSchema.optional(),
12736
+ // Dependencies configuration (external projects)
12737
+ dependencies: external_exports.record(DependencyConfigSchema).optional()
12664
12738
  }).strict();
12739
+ var FileSourceSchema = external_exports.object({
12740
+ type: external_exports.enum(["s3", "r2", "gcs", "local"]),
12741
+ bucket: external_exports.string().optional(),
12742
+ prefix: external_exports.string().optional(),
12743
+ region: external_exports.string().optional(),
12744
+ local: external_exports.object({
12745
+ base_path: external_exports.string()
12746
+ }),
12747
+ push: external_exports.object({
12748
+ compress: external_exports.boolean().optional(),
12749
+ keep_local: external_exports.boolean().optional()
12750
+ }).optional(),
12751
+ auth: external_exports.object({
12752
+ profile: external_exports.string().optional()
12753
+ }).optional()
12754
+ }).refine(
12755
+ (data) => {
12756
+ if (data.type !== "local" && !data.bucket) {
12757
+ return false;
12758
+ }
12759
+ return true;
12760
+ },
12761
+ {
12762
+ message: "Bucket is required for s3, r2, and gcs storage types",
12763
+ path: ["bucket"]
12764
+ }
12765
+ );
12766
+ var FileConfigSchema = external_exports.object({
12767
+ schema_version: external_exports.string(),
12768
+ sources: external_exports.record(FileSourceSchema)
12769
+ });
12770
+ external_exports.object({
12771
+ file: FileConfigSchema.optional(),
12772
+ codex: CodexConfigSchema.optional()
12773
+ });
12665
12774
  init_matcher();
12666
12775
  init_matcher();
12667
12776
  var DEFAULT_FETCH_OPTIONS = {
@@ -12680,8 +12789,8 @@ function mergeFetchOptions(options) {
12680
12789
  ...options
12681
12790
  };
12682
12791
  }
12683
- function detectContentType(path6) {
12684
- const ext = path6.split(".").pop()?.toLowerCase();
12792
+ function detectContentType(path7) {
12793
+ const ext = path7.split(".").pop()?.toLowerCase();
12685
12794
  const mimeTypes = {
12686
12795
  md: "text/markdown",
12687
12796
  markdown: "text/markdown",
@@ -13146,6 +13255,294 @@ var HttpStorage = class {
13146
13255
  }
13147
13256
  }
13148
13257
  };
13258
+ var FileSourceResolver = class {
13259
+ constructor(config) {
13260
+ this.config = config;
13261
+ this.initializeSources();
13262
+ }
13263
+ sources = /* @__PURE__ */ new Map();
13264
+ /**
13265
+ * Initialize sources from config
13266
+ */
13267
+ initializeSources() {
13268
+ if (!this.config.file?.sources) {
13269
+ return;
13270
+ }
13271
+ for (const [name, sourceConfig] of Object.entries(this.config.file.sources)) {
13272
+ const resolved = {
13273
+ name,
13274
+ type: "file-plugin",
13275
+ localPath: sourceConfig.local.base_path,
13276
+ isCurrentProject: true,
13277
+ config: sourceConfig
13278
+ };
13279
+ if (sourceConfig.bucket && sourceConfig.type !== "local") {
13280
+ resolved.bucket = sourceConfig.bucket;
13281
+ resolved.prefix = sourceConfig.prefix;
13282
+ resolved.remotePath = this.buildRemotePath(sourceConfig);
13283
+ }
13284
+ this.sources.set(name, resolved);
13285
+ }
13286
+ }
13287
+ /**
13288
+ * Build remote path from source config
13289
+ */
13290
+ buildRemotePath(source) {
13291
+ const protocol = source.type === "s3" ? "s3://" : source.type === "r2" ? "r2://" : "gcs://";
13292
+ const bucket = source.bucket;
13293
+ const prefix = source.prefix ? `/${source.prefix}` : "";
13294
+ return `${protocol}${bucket}${prefix}`;
13295
+ }
13296
+ /**
13297
+ * Get all available file plugin sources
13298
+ *
13299
+ * @returns Array of resolved file sources
13300
+ */
13301
+ getAvailableSources() {
13302
+ return Array.from(this.sources.values());
13303
+ }
13304
+ /**
13305
+ * Resolve a source by name
13306
+ *
13307
+ * @param name - Source name (e.g., "specs", "logs")
13308
+ * @returns Resolved source or null if not found
13309
+ */
13310
+ resolveSource(name) {
13311
+ return this.sources.get(name) || null;
13312
+ }
13313
+ /**
13314
+ * Check if a path belongs to any file plugin source
13315
+ *
13316
+ * @param path - File path to check
13317
+ * @returns True if path matches any source's base_path
13318
+ */
13319
+ isFilePluginPath(path7) {
13320
+ return this.getSourceForPath(path7) !== null;
13321
+ }
13322
+ /**
13323
+ * Get the source for a given path
13324
+ *
13325
+ * Matches path against all source base_paths and returns the matching source.
13326
+ * Uses longest-match strategy if multiple sources match.
13327
+ *
13328
+ * @param path - File path to match
13329
+ * @returns Matching source or null
13330
+ */
13331
+ getSourceForPath(path7) {
13332
+ let bestMatch = null;
13333
+ let bestMatchLength = 0;
13334
+ for (const source of this.sources.values()) {
13335
+ const normalizedPath = this.normalizePath(path7);
13336
+ const normalizedBasePath = this.normalizePath(source.localPath);
13337
+ if (normalizedPath.startsWith(normalizedBasePath)) {
13338
+ const matchLength = normalizedBasePath.length;
13339
+ if (matchLength > bestMatchLength) {
13340
+ bestMatch = source;
13341
+ bestMatchLength = matchLength;
13342
+ }
13343
+ }
13344
+ }
13345
+ return bestMatch;
13346
+ }
13347
+ /**
13348
+ * Normalize path for comparison
13349
+ * - Remove leading "./" or "/"
13350
+ * - Remove trailing "/"
13351
+ * - Convert to lowercase for case-insensitive comparison
13352
+ */
13353
+ normalizePath(path7) {
13354
+ return path7.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "").toLowerCase();
13355
+ }
13356
+ /**
13357
+ * Get source names
13358
+ *
13359
+ * @returns Array of source names
13360
+ */
13361
+ getSourceNames() {
13362
+ return Array.from(this.sources.keys());
13363
+ }
13364
+ /**
13365
+ * Check if sources are configured
13366
+ *
13367
+ * @returns True if any file plugin sources are configured
13368
+ */
13369
+ hasSources() {
13370
+ return this.sources.size > 0;
13371
+ }
13372
+ };
13373
+ var FilePluginFileNotFoundError = class _FilePluginFileNotFoundError extends Error {
13374
+ constructor(filePath, sourceName, options) {
13375
+ const includeCloudSuggestions = options?.includeCloudSuggestions !== false;
13376
+ const storageType = options?.storageType;
13377
+ let message = `File not found: ${filePath}
13378
+
13379
+ `;
13380
+ if (includeCloudSuggestions) {
13381
+ if (storageType) {
13382
+ message += `This file may be in cloud storage (${storageType}).
13383
+
13384
+ `;
13385
+ } else {
13386
+ message += `This file may not have been synced from remote storage yet.
13387
+ `;
13388
+ }
13389
+ message += `To fetch from cloud storage, run:
13390
+ `;
13391
+ message += ` file pull ${sourceName}
13392
+
13393
+ `;
13394
+ message += `Or sync all sources:
13395
+ `;
13396
+ message += ` file sync`;
13397
+ } else {
13398
+ message += `Please ensure the file exists locally or pull it from cloud storage.`;
13399
+ }
13400
+ super(message);
13401
+ this.filePath = filePath;
13402
+ this.sourceName = sourceName;
13403
+ this.name = "FilePluginFileNotFoundError";
13404
+ if (Error.captureStackTrace) {
13405
+ Error.captureStackTrace(this, _FilePluginFileNotFoundError);
13406
+ }
13407
+ }
13408
+ };
13409
+ var FilePluginStorage = class {
13410
+ constructor(options) {
13411
+ this.options = options;
13412
+ this.sourceResolver = new FileSourceResolver(options.config);
13413
+ this.baseDir = options.baseDir || process.cwd();
13414
+ }
13415
+ name = "file-plugin";
13416
+ type = "local";
13417
+ // Reuse local type for compatibility
13418
+ sourceResolver;
13419
+ baseDir;
13420
+ /**
13421
+ * Check if this provider can handle the reference
13422
+ *
13423
+ * Only handles:
13424
+ * - Current project references
13425
+ * - With sourceType === 'file-plugin'
13426
+ *
13427
+ * @param reference - Resolved reference
13428
+ * @returns True if this provider can handle the reference
13429
+ */
13430
+ canHandle(reference) {
13431
+ return reference.isCurrentProject && reference.sourceType === "file-plugin";
13432
+ }
13433
+ /**
13434
+ * Fetch content for a reference
13435
+ *
13436
+ * Reads from local filesystem based on the resolved local path.
13437
+ * If file not found and S3 fallback is enabled, throws helpful error.
13438
+ *
13439
+ * @param reference - Resolved reference
13440
+ * @param options - Fetch options (unused for local reads)
13441
+ * @returns Fetch result with content
13442
+ */
13443
+ async fetch(reference, _options) {
13444
+ if (!reference.localPath) {
13445
+ throw new Error(`File plugin reference missing localPath: ${reference.uri}`);
13446
+ }
13447
+ if (!reference.filePluginSource) {
13448
+ throw new Error(`File plugin reference missing source name: ${reference.uri}`);
13449
+ }
13450
+ const absolutePath = path3.isAbsolute(reference.localPath) ? path3.resolve(reference.localPath) : path3.resolve(this.baseDir, reference.localPath);
13451
+ const source = this.sourceResolver.resolveSource(reference.filePluginSource);
13452
+ if (source) {
13453
+ const allowedDir = path3.resolve(this.baseDir, source.localPath);
13454
+ if (!absolutePath.startsWith(allowedDir + path3.sep) && absolutePath !== allowedDir) {
13455
+ throw new Error(
13456
+ `Path traversal detected: ${reference.localPath} resolves outside allowed directory ${source.localPath}`
13457
+ );
13458
+ }
13459
+ }
13460
+ try {
13461
+ const content = await fs3.readFile(absolutePath);
13462
+ const contentType = this.detectContentType(absolutePath);
13463
+ return {
13464
+ content,
13465
+ contentType,
13466
+ size: content.length,
13467
+ source: "file-plugin",
13468
+ metadata: {
13469
+ filePluginSource: reference.filePluginSource,
13470
+ localPath: absolutePath
13471
+ }
13472
+ };
13473
+ } catch (error) {
13474
+ if (error.code === "ENOENT") {
13475
+ const source2 = this.sourceResolver.resolveSource(reference.filePluginSource);
13476
+ throw this.createFileNotFoundError(reference, source2);
13477
+ }
13478
+ throw error;
13479
+ }
13480
+ }
13481
+ /**
13482
+ * Check if a reference exists
13483
+ *
13484
+ * @param reference - Resolved reference
13485
+ * @param options - Fetch options (unused)
13486
+ * @returns True if file exists
13487
+ */
13488
+ async exists(reference, _options) {
13489
+ if (!reference.localPath) {
13490
+ return false;
13491
+ }
13492
+ const absolutePath = path3.isAbsolute(reference.localPath) ? path3.resolve(reference.localPath) : path3.resolve(this.baseDir, reference.localPath);
13493
+ if (reference.filePluginSource) {
13494
+ const source = this.sourceResolver.resolveSource(reference.filePluginSource);
13495
+ if (source) {
13496
+ const allowedDir = path3.resolve(this.baseDir, source.localPath);
13497
+ if (!absolutePath.startsWith(allowedDir + path3.sep) && absolutePath !== allowedDir) {
13498
+ return false;
13499
+ }
13500
+ }
13501
+ }
13502
+ try {
13503
+ await fs3.access(absolutePath);
13504
+ return true;
13505
+ } catch {
13506
+ return false;
13507
+ }
13508
+ }
13509
+ /**
13510
+ * Detect content type from file extension
13511
+ */
13512
+ detectContentType(filePath) {
13513
+ const ext = path3.extname(filePath).toLowerCase();
13514
+ const mimeTypes = {
13515
+ ".md": "text/markdown",
13516
+ ".txt": "text/plain",
13517
+ ".json": "application/json",
13518
+ ".yaml": "text/yaml",
13519
+ ".yml": "text/yaml",
13520
+ ".html": "text/html",
13521
+ ".xml": "application/xml",
13522
+ ".log": "text/plain",
13523
+ ".js": "application/javascript",
13524
+ ".ts": "application/typescript",
13525
+ ".py": "text/x-python",
13526
+ ".sh": "application/x-sh"
13527
+ };
13528
+ return mimeTypes[ext] || "application/octet-stream";
13529
+ }
13530
+ /**
13531
+ * Create a helpful error message when file is not found
13532
+ */
13533
+ createFileNotFoundError(reference, source) {
13534
+ const includeCloudSuggestions = this.options.enableS3Fallback !== false;
13535
+ const storageType = source?.config.type;
13536
+ return new FilePluginFileNotFoundError(
13537
+ reference.localPath || reference.path || "",
13538
+ reference.filePluginSource || "",
13539
+ {
13540
+ includeCloudSuggestions,
13541
+ storageType
13542
+ }
13543
+ );
13544
+ }
13545
+ };
13149
13546
  var execFileAsync = (0, import_util4.promisify)(import_child_process.execFile);
13150
13547
  async function execFileNoThrow(command, args = [], options) {
13151
13548
  try {
@@ -13293,10 +13690,10 @@ var S3ArchiveStorage = class {
13293
13690
  *
13294
13691
  * Used to organize archives by type
13295
13692
  */
13296
- detectType(path6) {
13297
- if (path6.startsWith("specs/")) return "specs";
13298
- if (path6.startsWith("docs/")) return "docs";
13299
- if (path6.includes("/logs/")) return "logs";
13693
+ detectType(path7) {
13694
+ if (path7.startsWith("specs/")) return "specs";
13695
+ if (path7.startsWith("docs/")) return "docs";
13696
+ if (path7.includes("/logs/")) return "logs";
13300
13697
  return "misc";
13301
13698
  }
13302
13699
  /**
@@ -13307,9 +13704,9 @@ var S3ArchiveStorage = class {
13307
13704
  * - *.md (all markdown files)
13308
13705
  * - docs/*.md (markdown files in docs/)
13309
13706
  */
13310
- matchesPatterns(path6, patterns) {
13707
+ matchesPatterns(path7, patterns) {
13311
13708
  for (const pattern of patterns) {
13312
- if (this.matchesPattern(path6, pattern)) {
13709
+ if (this.matchesPattern(path7, pattern)) {
13313
13710
  return true;
13314
13711
  }
13315
13712
  }
@@ -13318,27 +13715,74 @@ var S3ArchiveStorage = class {
13318
13715
  /**
13319
13716
  * Check if path matches a single pattern
13320
13717
  */
13321
- matchesPattern(path6, pattern) {
13718
+ matchesPattern(path7, pattern) {
13322
13719
  const DOUBLE_STAR = "\0DOUBLE_STAR\0";
13323
13720
  let regexPattern = pattern.replace(/\*\*/g, DOUBLE_STAR);
13324
13721
  regexPattern = regexPattern.replace(/[.[\](){}+^$|\\]/g, "\\$&");
13325
13722
  regexPattern = regexPattern.replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
13326
13723
  regexPattern = regexPattern.replace(new RegExp(DOUBLE_STAR, "g"), ".*");
13327
13724
  const regex = new RegExp(`^${regexPattern}$`);
13328
- return regex.test(path6);
13725
+ return regex.test(path7);
13329
13726
  }
13330
13727
  };
13331
13728
  var StorageManager = class {
13332
13729
  providers = /* @__PURE__ */ new Map();
13333
13730
  priority;
13731
+ codexConfig;
13334
13732
  constructor(config = {}) {
13733
+ this.codexConfig = config.codexConfig;
13335
13734
  this.providers.set("local", new LocalStorage(config.local));
13336
13735
  this.providers.set("github", new GitHubStorage(config.github));
13337
13736
  this.providers.set("http", new HttpStorage(config.http));
13338
13737
  if (config.s3Archive) {
13339
13738
  this.providers.set("s3-archive", new S3ArchiveStorage(config.s3Archive));
13340
13739
  }
13341
- this.priority = config.priority || (config.s3Archive ? ["local", "s3-archive", "github", "http"] : ["local", "github", "http"]);
13740
+ if (config.filePlugin) {
13741
+ this.providers.set("file-plugin", new FilePluginStorage(config.filePlugin));
13742
+ }
13743
+ 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"]);
13744
+ }
13745
+ /**
13746
+ * Resolve authentication token for a reference
13747
+ *
13748
+ * Looks up dependency-specific authentication or falls back to default
13749
+ */
13750
+ resolveToken(reference) {
13751
+ if (!this.codexConfig) {
13752
+ return void 0;
13753
+ }
13754
+ const dependencyKey = `${reference.org}/${reference.project}`;
13755
+ if (this.codexConfig.dependencies?.[dependencyKey]) {
13756
+ const dependency = this.codexConfig.dependencies[dependencyKey];
13757
+ for (const [, sourceConfig] of Object.entries(dependency.sources)) {
13758
+ if (sourceConfig.type === "github") {
13759
+ if (sourceConfig.token_env) {
13760
+ const token = process.env[sourceConfig.token_env];
13761
+ if (token) {
13762
+ return token;
13763
+ }
13764
+ }
13765
+ if (sourceConfig.token) {
13766
+ return sourceConfig.token;
13767
+ }
13768
+ }
13769
+ }
13770
+ }
13771
+ const defaultTokenEnv = this.codexConfig.auth?.github?.default_token_env || "GITHUB_TOKEN";
13772
+ return process.env[defaultTokenEnv];
13773
+ }
13774
+ /**
13775
+ * Resolve fetch options with authentication
13776
+ *
13777
+ * Merges reference-specific authentication with provided options
13778
+ */
13779
+ resolveFetchOptions(reference, options) {
13780
+ const token = this.resolveToken(reference);
13781
+ return {
13782
+ ...options,
13783
+ token: options?.token || token
13784
+ // Explicit option overrides resolved token
13785
+ };
13342
13786
  }
13343
13787
  /**
13344
13788
  * Register a custom storage provider
@@ -13380,8 +13824,10 @@ var StorageManager = class {
13380
13824
  * Fetch content for a reference
13381
13825
  *
13382
13826
  * Tries providers in priority order until one succeeds.
13827
+ * Automatically resolves authentication based on dependency configuration.
13383
13828
  */
13384
13829
  async fetch(reference, options) {
13830
+ const resolvedOptions = this.resolveFetchOptions(reference, options);
13385
13831
  const errors = [];
13386
13832
  for (const type2 of this.priority) {
13387
13833
  const provider = this.providers.get(type2);
@@ -13389,7 +13835,7 @@ var StorageManager = class {
13389
13835
  continue;
13390
13836
  }
13391
13837
  try {
13392
- return await provider.fetch(reference, options);
13838
+ return await provider.fetch(reference, resolvedOptions);
13393
13839
  } catch (error) {
13394
13840
  errors.push(error instanceof Error ? error : new Error(String(error)));
13395
13841
  }
@@ -13410,15 +13856,17 @@ var StorageManager = class {
13410
13856
  * Check if content exists for a reference
13411
13857
  *
13412
13858
  * Returns true if any provider reports the content exists.
13859
+ * Automatically resolves authentication based on dependency configuration.
13413
13860
  */
13414
13861
  async exists(reference, options) {
13862
+ const resolvedOptions = this.resolveFetchOptions(reference, options);
13415
13863
  for (const type2 of this.priority) {
13416
13864
  const provider = this.providers.get(type2);
13417
13865
  if (!provider || !provider.canHandle(reference)) {
13418
13866
  continue;
13419
13867
  }
13420
13868
  try {
13421
- if (await provider.exists(reference, options)) {
13869
+ if (await provider.exists(reference, resolvedOptions)) {
13422
13870
  return true;
13423
13871
  }
13424
13872
  } catch {
@@ -13438,6 +13886,8 @@ var StorageManager = class {
13438
13886
  }
13439
13887
  /**
13440
13888
  * Fetch multiple references in parallel
13889
+ *
13890
+ * Automatically resolves authentication for each reference based on dependency configuration.
13441
13891
  */
13442
13892
  async fetchMany(references, options) {
13443
13893
  const results = /* @__PURE__ */ new Map();
@@ -13806,8 +14256,17 @@ var CacheManager = class {
13806
14256
  * Get content for a reference
13807
14257
  *
13808
14258
  * Implements cache-first strategy with stale-while-revalidate.
14259
+ *
14260
+ * EXCEPTION: File plugin sources (current project files) bypass cache entirely.
14261
+ * They are always read fresh from disk for optimal development experience.
13809
14262
  */
13810
14263
  async get(reference, options) {
14264
+ if (reference.isCurrentProject && reference.sourceType === "file-plugin") {
14265
+ if (!this.storage) {
14266
+ throw new Error("Storage manager not set");
14267
+ }
14268
+ return await this.storage.fetch(reference, options);
14269
+ }
13811
14270
  const ttl = options?.ttl ?? this.config.defaultTtl;
13812
14271
  let entry = this.memoryCache.get(reference.uri);
13813
14272
  if (!entry && this.persistence) {
@@ -14013,13 +14472,18 @@ var CacheManager = class {
14013
14472
  }
14014
14473
  /**
14015
14474
  * Fetch content and store in cache
14475
+ *
14476
+ * EXCEPTION: File plugin sources are not cached (should not reach here,
14477
+ * but added as safety check).
14016
14478
  */
14017
14479
  async fetchAndCache(reference, ttl, options) {
14018
14480
  if (!this.storage) {
14019
14481
  throw new Error("Storage manager not set");
14020
14482
  }
14021
14483
  const result = await this.storage.fetch(reference, options);
14022
- await this.set(reference.uri, result, ttl);
14484
+ if (!(reference.isCurrentProject && reference.sourceType === "file-plugin")) {
14485
+ await this.set(reference.uri, result, ttl);
14486
+ }
14023
14487
  return result;
14024
14488
  }
14025
14489
  /**
@@ -14203,6 +14667,14 @@ var CODEX_TOOLS = [
14203
14667
  },
14204
14668
  required: ["pattern"]
14205
14669
  }
14670
+ },
14671
+ {
14672
+ name: "codex_file_sources_list",
14673
+ description: "List file plugin sources available in the current project.",
14674
+ inputSchema: {
14675
+ type: "object",
14676
+ properties: {}
14677
+ }
14206
14678
  }
14207
14679
  ];
14208
14680
  function textResult(text, isError = false) {
@@ -14239,9 +14711,12 @@ async function handleFetch(args, ctx) {
14239
14711
  }
14240
14712
  try {
14241
14713
  let result;
14714
+ const isFilePlugin = ref.isCurrentProject && ref.sourceType === "file-plugin";
14242
14715
  if (noCache) {
14243
14716
  result = await ctx.storage.fetch(ref, { branch });
14244
- await ctx.cache.set(uri, result);
14717
+ if (!isFilePlugin) {
14718
+ await ctx.cache.set(uri, result);
14719
+ }
14245
14720
  } else {
14246
14721
  result = await ctx.cache.get(ref, { branch });
14247
14722
  }
@@ -14249,6 +14724,10 @@ async function handleFetch(args, ctx) {
14249
14724
  return resourceResult(uri, content, result.contentType);
14250
14725
  } catch (error) {
14251
14726
  const message = error instanceof Error ? error.message : String(error);
14727
+ const errorName = error instanceof Error ? error.name : "Error";
14728
+ if (errorName === "FilePluginFileNotFoundError") {
14729
+ return textResult(message, true);
14730
+ }
14252
14731
  return textResult(`Failed to fetch ${uri}: ${message}`, true);
14253
14732
  }
14254
14733
  }
@@ -14347,6 +14826,33 @@ async function handleCacheClear(args, ctx) {
14347
14826
  return textResult(`Failed to clear cache: ${message}`, true);
14348
14827
  }
14349
14828
  }
14829
+ async function handleFileSourcesList(ctx) {
14830
+ try {
14831
+ const filePluginProvider = ctx.storage.getProvider("file-plugin");
14832
+ if (!filePluginProvider) {
14833
+ return textResult(`No file plugin sources configured.
14834
+
14835
+ To enable file plugin integration, the storage manager must be initialized with filePlugin configuration.
14836
+
14837
+ File plugin sources allow codex to access current project artifacts (specs, logs, etc.) directly from the local filesystem without caching.`);
14838
+ }
14839
+ return textResult(`File plugin provider is configured.
14840
+
14841
+ File plugin sources are available for current project artifact access.
14842
+ Sources are read directly from local filesystem without caching.
14843
+
14844
+ To see available sources, check the unified configuration at:
14845
+ .fractary/config.yaml
14846
+
14847
+ Look for the 'file.sources' section which defines:
14848
+ - specs: .fractary/specs
14849
+ - logs: .fractary/logs
14850
+ - etc.`);
14851
+ } catch (error) {
14852
+ const message = error instanceof Error ? error.message : String(error);
14853
+ return textResult(`Failed to list file sources: ${message}`, true);
14854
+ }
14855
+ }
14350
14856
  async function handleToolCall(name, args, ctx) {
14351
14857
  switch (name) {
14352
14858
  case "codex_document_fetch":
@@ -14357,6 +14863,8 @@ async function handleToolCall(name, args, ctx) {
14357
14863
  return handleList(args, ctx);
14358
14864
  case "codex_cache_clear":
14359
14865
  return handleCacheClear(args, ctx);
14866
+ case "codex_file_sources_list":
14867
+ return handleFileSourcesList(ctx);
14360
14868
  default:
14361
14869
  return textResult(`Unknown tool: ${name}`, true);
14362
14870
  }