@fractary/codex-mcp 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/cli.cjs +435 -13
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +116 -1
- package/dist/index.cjs.map +1 -1
- package/package.json +2 -2
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,
|
|
@@ -12662,6 +12700,41 @@ var CodexConfigSchema = external_exports.object({
|
|
|
12662
12700
|
// Archive configuration
|
|
12663
12701
|
archive: ArchiveConfigSchema.optional()
|
|
12664
12702
|
}).strict();
|
|
12703
|
+
var FileSourceSchema = external_exports.object({
|
|
12704
|
+
type: external_exports.enum(["s3", "r2", "gcs", "local"]),
|
|
12705
|
+
bucket: external_exports.string().optional(),
|
|
12706
|
+
prefix: external_exports.string().optional(),
|
|
12707
|
+
region: external_exports.string().optional(),
|
|
12708
|
+
local: external_exports.object({
|
|
12709
|
+
base_path: external_exports.string()
|
|
12710
|
+
}),
|
|
12711
|
+
push: external_exports.object({
|
|
12712
|
+
compress: external_exports.boolean().optional(),
|
|
12713
|
+
keep_local: external_exports.boolean().optional()
|
|
12714
|
+
}).optional(),
|
|
12715
|
+
auth: external_exports.object({
|
|
12716
|
+
profile: external_exports.string().optional()
|
|
12717
|
+
}).optional()
|
|
12718
|
+
}).refine(
|
|
12719
|
+
(data) => {
|
|
12720
|
+
if (data.type !== "local" && !data.bucket) {
|
|
12721
|
+
return false;
|
|
12722
|
+
}
|
|
12723
|
+
return true;
|
|
12724
|
+
},
|
|
12725
|
+
{
|
|
12726
|
+
message: "Bucket is required for s3, r2, and gcs storage types",
|
|
12727
|
+
path: ["bucket"]
|
|
12728
|
+
}
|
|
12729
|
+
);
|
|
12730
|
+
var FileConfigSchema = external_exports.object({
|
|
12731
|
+
schema_version: external_exports.string(),
|
|
12732
|
+
sources: external_exports.record(FileSourceSchema)
|
|
12733
|
+
});
|
|
12734
|
+
external_exports.object({
|
|
12735
|
+
file: FileConfigSchema.optional(),
|
|
12736
|
+
codex: CodexConfigSchema.optional()
|
|
12737
|
+
});
|
|
12665
12738
|
init_matcher();
|
|
12666
12739
|
init_matcher();
|
|
12667
12740
|
var DEFAULT_FETCH_OPTIONS = {
|
|
@@ -12680,8 +12753,8 @@ function mergeFetchOptions(options) {
|
|
|
12680
12753
|
...options
|
|
12681
12754
|
};
|
|
12682
12755
|
}
|
|
12683
|
-
function detectContentType(
|
|
12684
|
-
const ext =
|
|
12756
|
+
function detectContentType(path7) {
|
|
12757
|
+
const ext = path7.split(".").pop()?.toLowerCase();
|
|
12685
12758
|
const mimeTypes = {
|
|
12686
12759
|
md: "text/markdown",
|
|
12687
12760
|
markdown: "text/markdown",
|
|
@@ -13146,6 +13219,294 @@ var HttpStorage = class {
|
|
|
13146
13219
|
}
|
|
13147
13220
|
}
|
|
13148
13221
|
};
|
|
13222
|
+
var FileSourceResolver = class {
|
|
13223
|
+
constructor(config) {
|
|
13224
|
+
this.config = config;
|
|
13225
|
+
this.initializeSources();
|
|
13226
|
+
}
|
|
13227
|
+
sources = /* @__PURE__ */ new Map();
|
|
13228
|
+
/**
|
|
13229
|
+
* Initialize sources from config
|
|
13230
|
+
*/
|
|
13231
|
+
initializeSources() {
|
|
13232
|
+
if (!this.config.file?.sources) {
|
|
13233
|
+
return;
|
|
13234
|
+
}
|
|
13235
|
+
for (const [name, sourceConfig] of Object.entries(this.config.file.sources)) {
|
|
13236
|
+
const resolved = {
|
|
13237
|
+
name,
|
|
13238
|
+
type: "file-plugin",
|
|
13239
|
+
localPath: sourceConfig.local.base_path,
|
|
13240
|
+
isCurrentProject: true,
|
|
13241
|
+
config: sourceConfig
|
|
13242
|
+
};
|
|
13243
|
+
if (sourceConfig.bucket && sourceConfig.type !== "local") {
|
|
13244
|
+
resolved.bucket = sourceConfig.bucket;
|
|
13245
|
+
resolved.prefix = sourceConfig.prefix;
|
|
13246
|
+
resolved.remotePath = this.buildRemotePath(sourceConfig);
|
|
13247
|
+
}
|
|
13248
|
+
this.sources.set(name, resolved);
|
|
13249
|
+
}
|
|
13250
|
+
}
|
|
13251
|
+
/**
|
|
13252
|
+
* Build remote path from source config
|
|
13253
|
+
*/
|
|
13254
|
+
buildRemotePath(source) {
|
|
13255
|
+
const protocol = source.type === "s3" ? "s3://" : source.type === "r2" ? "r2://" : "gcs://";
|
|
13256
|
+
const bucket = source.bucket;
|
|
13257
|
+
const prefix = source.prefix ? `/${source.prefix}` : "";
|
|
13258
|
+
return `${protocol}${bucket}${prefix}`;
|
|
13259
|
+
}
|
|
13260
|
+
/**
|
|
13261
|
+
* Get all available file plugin sources
|
|
13262
|
+
*
|
|
13263
|
+
* @returns Array of resolved file sources
|
|
13264
|
+
*/
|
|
13265
|
+
getAvailableSources() {
|
|
13266
|
+
return Array.from(this.sources.values());
|
|
13267
|
+
}
|
|
13268
|
+
/**
|
|
13269
|
+
* Resolve a source by name
|
|
13270
|
+
*
|
|
13271
|
+
* @param name - Source name (e.g., "specs", "logs")
|
|
13272
|
+
* @returns Resolved source or null if not found
|
|
13273
|
+
*/
|
|
13274
|
+
resolveSource(name) {
|
|
13275
|
+
return this.sources.get(name) || null;
|
|
13276
|
+
}
|
|
13277
|
+
/**
|
|
13278
|
+
* Check if a path belongs to any file plugin source
|
|
13279
|
+
*
|
|
13280
|
+
* @param path - File path to check
|
|
13281
|
+
* @returns True if path matches any source's base_path
|
|
13282
|
+
*/
|
|
13283
|
+
isFilePluginPath(path7) {
|
|
13284
|
+
return this.getSourceForPath(path7) !== null;
|
|
13285
|
+
}
|
|
13286
|
+
/**
|
|
13287
|
+
* Get the source for a given path
|
|
13288
|
+
*
|
|
13289
|
+
* Matches path against all source base_paths and returns the matching source.
|
|
13290
|
+
* Uses longest-match strategy if multiple sources match.
|
|
13291
|
+
*
|
|
13292
|
+
* @param path - File path to match
|
|
13293
|
+
* @returns Matching source or null
|
|
13294
|
+
*/
|
|
13295
|
+
getSourceForPath(path7) {
|
|
13296
|
+
let bestMatch = null;
|
|
13297
|
+
let bestMatchLength = 0;
|
|
13298
|
+
for (const source of this.sources.values()) {
|
|
13299
|
+
const normalizedPath = this.normalizePath(path7);
|
|
13300
|
+
const normalizedBasePath = this.normalizePath(source.localPath);
|
|
13301
|
+
if (normalizedPath.startsWith(normalizedBasePath)) {
|
|
13302
|
+
const matchLength = normalizedBasePath.length;
|
|
13303
|
+
if (matchLength > bestMatchLength) {
|
|
13304
|
+
bestMatch = source;
|
|
13305
|
+
bestMatchLength = matchLength;
|
|
13306
|
+
}
|
|
13307
|
+
}
|
|
13308
|
+
}
|
|
13309
|
+
return bestMatch;
|
|
13310
|
+
}
|
|
13311
|
+
/**
|
|
13312
|
+
* Normalize path for comparison
|
|
13313
|
+
* - Remove leading "./" or "/"
|
|
13314
|
+
* - Remove trailing "/"
|
|
13315
|
+
* - Convert to lowercase for case-insensitive comparison
|
|
13316
|
+
*/
|
|
13317
|
+
normalizePath(path7) {
|
|
13318
|
+
return path7.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "").toLowerCase();
|
|
13319
|
+
}
|
|
13320
|
+
/**
|
|
13321
|
+
* Get source names
|
|
13322
|
+
*
|
|
13323
|
+
* @returns Array of source names
|
|
13324
|
+
*/
|
|
13325
|
+
getSourceNames() {
|
|
13326
|
+
return Array.from(this.sources.keys());
|
|
13327
|
+
}
|
|
13328
|
+
/**
|
|
13329
|
+
* Check if sources are configured
|
|
13330
|
+
*
|
|
13331
|
+
* @returns True if any file plugin sources are configured
|
|
13332
|
+
*/
|
|
13333
|
+
hasSources() {
|
|
13334
|
+
return this.sources.size > 0;
|
|
13335
|
+
}
|
|
13336
|
+
};
|
|
13337
|
+
var FilePluginFileNotFoundError = class _FilePluginFileNotFoundError extends Error {
|
|
13338
|
+
constructor(filePath, sourceName, options) {
|
|
13339
|
+
const includeCloudSuggestions = options?.includeCloudSuggestions !== false;
|
|
13340
|
+
const storageType = options?.storageType;
|
|
13341
|
+
let message = `File not found: ${filePath}
|
|
13342
|
+
|
|
13343
|
+
`;
|
|
13344
|
+
if (includeCloudSuggestions) {
|
|
13345
|
+
if (storageType) {
|
|
13346
|
+
message += `This file may be in cloud storage (${storageType}).
|
|
13347
|
+
|
|
13348
|
+
`;
|
|
13349
|
+
} else {
|
|
13350
|
+
message += `This file may not have been synced from remote storage yet.
|
|
13351
|
+
`;
|
|
13352
|
+
}
|
|
13353
|
+
message += `To fetch from cloud storage, run:
|
|
13354
|
+
`;
|
|
13355
|
+
message += ` file pull ${sourceName}
|
|
13356
|
+
|
|
13357
|
+
`;
|
|
13358
|
+
message += `Or sync all sources:
|
|
13359
|
+
`;
|
|
13360
|
+
message += ` file sync`;
|
|
13361
|
+
} else {
|
|
13362
|
+
message += `Please ensure the file exists locally or pull it from cloud storage.`;
|
|
13363
|
+
}
|
|
13364
|
+
super(message);
|
|
13365
|
+
this.filePath = filePath;
|
|
13366
|
+
this.sourceName = sourceName;
|
|
13367
|
+
this.name = "FilePluginFileNotFoundError";
|
|
13368
|
+
if (Error.captureStackTrace) {
|
|
13369
|
+
Error.captureStackTrace(this, _FilePluginFileNotFoundError);
|
|
13370
|
+
}
|
|
13371
|
+
}
|
|
13372
|
+
};
|
|
13373
|
+
var FilePluginStorage = class {
|
|
13374
|
+
constructor(options) {
|
|
13375
|
+
this.options = options;
|
|
13376
|
+
this.sourceResolver = new FileSourceResolver(options.config);
|
|
13377
|
+
this.baseDir = options.baseDir || process.cwd();
|
|
13378
|
+
}
|
|
13379
|
+
name = "file-plugin";
|
|
13380
|
+
type = "local";
|
|
13381
|
+
// Reuse local type for compatibility
|
|
13382
|
+
sourceResolver;
|
|
13383
|
+
baseDir;
|
|
13384
|
+
/**
|
|
13385
|
+
* Check if this provider can handle the reference
|
|
13386
|
+
*
|
|
13387
|
+
* Only handles:
|
|
13388
|
+
* - Current project references
|
|
13389
|
+
* - With sourceType === 'file-plugin'
|
|
13390
|
+
*
|
|
13391
|
+
* @param reference - Resolved reference
|
|
13392
|
+
* @returns True if this provider can handle the reference
|
|
13393
|
+
*/
|
|
13394
|
+
canHandle(reference) {
|
|
13395
|
+
return reference.isCurrentProject && reference.sourceType === "file-plugin";
|
|
13396
|
+
}
|
|
13397
|
+
/**
|
|
13398
|
+
* Fetch content for a reference
|
|
13399
|
+
*
|
|
13400
|
+
* Reads from local filesystem based on the resolved local path.
|
|
13401
|
+
* If file not found and S3 fallback is enabled, throws helpful error.
|
|
13402
|
+
*
|
|
13403
|
+
* @param reference - Resolved reference
|
|
13404
|
+
* @param options - Fetch options (unused for local reads)
|
|
13405
|
+
* @returns Fetch result with content
|
|
13406
|
+
*/
|
|
13407
|
+
async fetch(reference, _options) {
|
|
13408
|
+
if (!reference.localPath) {
|
|
13409
|
+
throw new Error(`File plugin reference missing localPath: ${reference.uri}`);
|
|
13410
|
+
}
|
|
13411
|
+
if (!reference.filePluginSource) {
|
|
13412
|
+
throw new Error(`File plugin reference missing source name: ${reference.uri}`);
|
|
13413
|
+
}
|
|
13414
|
+
const absolutePath = path3.isAbsolute(reference.localPath) ? path3.resolve(reference.localPath) : path3.resolve(this.baseDir, reference.localPath);
|
|
13415
|
+
const source = this.sourceResolver.resolveSource(reference.filePluginSource);
|
|
13416
|
+
if (source) {
|
|
13417
|
+
const allowedDir = path3.resolve(this.baseDir, source.localPath);
|
|
13418
|
+
if (!absolutePath.startsWith(allowedDir + path3.sep) && absolutePath !== allowedDir) {
|
|
13419
|
+
throw new Error(
|
|
13420
|
+
`Path traversal detected: ${reference.localPath} resolves outside allowed directory ${source.localPath}`
|
|
13421
|
+
);
|
|
13422
|
+
}
|
|
13423
|
+
}
|
|
13424
|
+
try {
|
|
13425
|
+
const content = await fs3.readFile(absolutePath);
|
|
13426
|
+
const contentType = this.detectContentType(absolutePath);
|
|
13427
|
+
return {
|
|
13428
|
+
content,
|
|
13429
|
+
contentType,
|
|
13430
|
+
size: content.length,
|
|
13431
|
+
source: "file-plugin",
|
|
13432
|
+
metadata: {
|
|
13433
|
+
filePluginSource: reference.filePluginSource,
|
|
13434
|
+
localPath: absolutePath
|
|
13435
|
+
}
|
|
13436
|
+
};
|
|
13437
|
+
} catch (error) {
|
|
13438
|
+
if (error.code === "ENOENT") {
|
|
13439
|
+
const source2 = this.sourceResolver.resolveSource(reference.filePluginSource);
|
|
13440
|
+
throw this.createFileNotFoundError(reference, source2);
|
|
13441
|
+
}
|
|
13442
|
+
throw error;
|
|
13443
|
+
}
|
|
13444
|
+
}
|
|
13445
|
+
/**
|
|
13446
|
+
* Check if a reference exists
|
|
13447
|
+
*
|
|
13448
|
+
* @param reference - Resolved reference
|
|
13449
|
+
* @param options - Fetch options (unused)
|
|
13450
|
+
* @returns True if file exists
|
|
13451
|
+
*/
|
|
13452
|
+
async exists(reference, _options) {
|
|
13453
|
+
if (!reference.localPath) {
|
|
13454
|
+
return false;
|
|
13455
|
+
}
|
|
13456
|
+
const absolutePath = path3.isAbsolute(reference.localPath) ? path3.resolve(reference.localPath) : path3.resolve(this.baseDir, reference.localPath);
|
|
13457
|
+
if (reference.filePluginSource) {
|
|
13458
|
+
const source = this.sourceResolver.resolveSource(reference.filePluginSource);
|
|
13459
|
+
if (source) {
|
|
13460
|
+
const allowedDir = path3.resolve(this.baseDir, source.localPath);
|
|
13461
|
+
if (!absolutePath.startsWith(allowedDir + path3.sep) && absolutePath !== allowedDir) {
|
|
13462
|
+
return false;
|
|
13463
|
+
}
|
|
13464
|
+
}
|
|
13465
|
+
}
|
|
13466
|
+
try {
|
|
13467
|
+
await fs3.access(absolutePath);
|
|
13468
|
+
return true;
|
|
13469
|
+
} catch {
|
|
13470
|
+
return false;
|
|
13471
|
+
}
|
|
13472
|
+
}
|
|
13473
|
+
/**
|
|
13474
|
+
* Detect content type from file extension
|
|
13475
|
+
*/
|
|
13476
|
+
detectContentType(filePath) {
|
|
13477
|
+
const ext = path3.extname(filePath).toLowerCase();
|
|
13478
|
+
const mimeTypes = {
|
|
13479
|
+
".md": "text/markdown",
|
|
13480
|
+
".txt": "text/plain",
|
|
13481
|
+
".json": "application/json",
|
|
13482
|
+
".yaml": "text/yaml",
|
|
13483
|
+
".yml": "text/yaml",
|
|
13484
|
+
".html": "text/html",
|
|
13485
|
+
".xml": "application/xml",
|
|
13486
|
+
".log": "text/plain",
|
|
13487
|
+
".js": "application/javascript",
|
|
13488
|
+
".ts": "application/typescript",
|
|
13489
|
+
".py": "text/x-python",
|
|
13490
|
+
".sh": "application/x-sh"
|
|
13491
|
+
};
|
|
13492
|
+
return mimeTypes[ext] || "application/octet-stream";
|
|
13493
|
+
}
|
|
13494
|
+
/**
|
|
13495
|
+
* Create a helpful error message when file is not found
|
|
13496
|
+
*/
|
|
13497
|
+
createFileNotFoundError(reference, source) {
|
|
13498
|
+
const includeCloudSuggestions = this.options.enableS3Fallback !== false;
|
|
13499
|
+
const storageType = source?.config.type;
|
|
13500
|
+
return new FilePluginFileNotFoundError(
|
|
13501
|
+
reference.localPath || reference.path || "",
|
|
13502
|
+
reference.filePluginSource || "",
|
|
13503
|
+
{
|
|
13504
|
+
includeCloudSuggestions,
|
|
13505
|
+
storageType
|
|
13506
|
+
}
|
|
13507
|
+
);
|
|
13508
|
+
}
|
|
13509
|
+
};
|
|
13149
13510
|
var execFileAsync = (0, import_util4.promisify)(import_child_process.execFile);
|
|
13150
13511
|
async function execFileNoThrow(command, args = [], options) {
|
|
13151
13512
|
try {
|
|
@@ -13293,10 +13654,10 @@ var S3ArchiveStorage = class {
|
|
|
13293
13654
|
*
|
|
13294
13655
|
* Used to organize archives by type
|
|
13295
13656
|
*/
|
|
13296
|
-
detectType(
|
|
13297
|
-
if (
|
|
13298
|
-
if (
|
|
13299
|
-
if (
|
|
13657
|
+
detectType(path7) {
|
|
13658
|
+
if (path7.startsWith("specs/")) return "specs";
|
|
13659
|
+
if (path7.startsWith("docs/")) return "docs";
|
|
13660
|
+
if (path7.includes("/logs/")) return "logs";
|
|
13300
13661
|
return "misc";
|
|
13301
13662
|
}
|
|
13302
13663
|
/**
|
|
@@ -13307,9 +13668,9 @@ var S3ArchiveStorage = class {
|
|
|
13307
13668
|
* - *.md (all markdown files)
|
|
13308
13669
|
* - docs/*.md (markdown files in docs/)
|
|
13309
13670
|
*/
|
|
13310
|
-
matchesPatterns(
|
|
13671
|
+
matchesPatterns(path7, patterns) {
|
|
13311
13672
|
for (const pattern of patterns) {
|
|
13312
|
-
if (this.matchesPattern(
|
|
13673
|
+
if (this.matchesPattern(path7, pattern)) {
|
|
13313
13674
|
return true;
|
|
13314
13675
|
}
|
|
13315
13676
|
}
|
|
@@ -13318,14 +13679,14 @@ var S3ArchiveStorage = class {
|
|
|
13318
13679
|
/**
|
|
13319
13680
|
* Check if path matches a single pattern
|
|
13320
13681
|
*/
|
|
13321
|
-
matchesPattern(
|
|
13682
|
+
matchesPattern(path7, pattern) {
|
|
13322
13683
|
const DOUBLE_STAR = "\0DOUBLE_STAR\0";
|
|
13323
13684
|
let regexPattern = pattern.replace(/\*\*/g, DOUBLE_STAR);
|
|
13324
13685
|
regexPattern = regexPattern.replace(/[.[\](){}+^$|\\]/g, "\\$&");
|
|
13325
13686
|
regexPattern = regexPattern.replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
|
|
13326
13687
|
regexPattern = regexPattern.replace(new RegExp(DOUBLE_STAR, "g"), ".*");
|
|
13327
13688
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
13328
|
-
return regex.test(
|
|
13689
|
+
return regex.test(path7);
|
|
13329
13690
|
}
|
|
13330
13691
|
};
|
|
13331
13692
|
var StorageManager = class {
|
|
@@ -13338,7 +13699,10 @@ var StorageManager = class {
|
|
|
13338
13699
|
if (config.s3Archive) {
|
|
13339
13700
|
this.providers.set("s3-archive", new S3ArchiveStorage(config.s3Archive));
|
|
13340
13701
|
}
|
|
13341
|
-
|
|
13702
|
+
if (config.filePlugin) {
|
|
13703
|
+
this.providers.set("file-plugin", new FilePluginStorage(config.filePlugin));
|
|
13704
|
+
}
|
|
13705
|
+
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"]);
|
|
13342
13706
|
}
|
|
13343
13707
|
/**
|
|
13344
13708
|
* Register a custom storage provider
|
|
@@ -13806,8 +14170,17 @@ var CacheManager = class {
|
|
|
13806
14170
|
* Get content for a reference
|
|
13807
14171
|
*
|
|
13808
14172
|
* Implements cache-first strategy with stale-while-revalidate.
|
|
14173
|
+
*
|
|
14174
|
+
* EXCEPTION: File plugin sources (current project files) bypass cache entirely.
|
|
14175
|
+
* They are always read fresh from disk for optimal development experience.
|
|
13809
14176
|
*/
|
|
13810
14177
|
async get(reference, options) {
|
|
14178
|
+
if (reference.isCurrentProject && reference.sourceType === "file-plugin") {
|
|
14179
|
+
if (!this.storage) {
|
|
14180
|
+
throw new Error("Storage manager not set");
|
|
14181
|
+
}
|
|
14182
|
+
return await this.storage.fetch(reference, options);
|
|
14183
|
+
}
|
|
13811
14184
|
const ttl = options?.ttl ?? this.config.defaultTtl;
|
|
13812
14185
|
let entry = this.memoryCache.get(reference.uri);
|
|
13813
14186
|
if (!entry && this.persistence) {
|
|
@@ -14013,13 +14386,18 @@ var CacheManager = class {
|
|
|
14013
14386
|
}
|
|
14014
14387
|
/**
|
|
14015
14388
|
* Fetch content and store in cache
|
|
14389
|
+
*
|
|
14390
|
+
* EXCEPTION: File plugin sources are not cached (should not reach here,
|
|
14391
|
+
* but added as safety check).
|
|
14016
14392
|
*/
|
|
14017
14393
|
async fetchAndCache(reference, ttl, options) {
|
|
14018
14394
|
if (!this.storage) {
|
|
14019
14395
|
throw new Error("Storage manager not set");
|
|
14020
14396
|
}
|
|
14021
14397
|
const result = await this.storage.fetch(reference, options);
|
|
14022
|
-
|
|
14398
|
+
if (!(reference.isCurrentProject && reference.sourceType === "file-plugin")) {
|
|
14399
|
+
await this.set(reference.uri, result, ttl);
|
|
14400
|
+
}
|
|
14023
14401
|
return result;
|
|
14024
14402
|
}
|
|
14025
14403
|
/**
|
|
@@ -14203,6 +14581,14 @@ var CODEX_TOOLS = [
|
|
|
14203
14581
|
},
|
|
14204
14582
|
required: ["pattern"]
|
|
14205
14583
|
}
|
|
14584
|
+
},
|
|
14585
|
+
{
|
|
14586
|
+
name: "codex_file_sources_list",
|
|
14587
|
+
description: "List file plugin sources available in the current project.",
|
|
14588
|
+
inputSchema: {
|
|
14589
|
+
type: "object",
|
|
14590
|
+
properties: {}
|
|
14591
|
+
}
|
|
14206
14592
|
}
|
|
14207
14593
|
];
|
|
14208
14594
|
function textResult(text, isError = false) {
|
|
@@ -14239,9 +14625,12 @@ async function handleFetch(args, ctx) {
|
|
|
14239
14625
|
}
|
|
14240
14626
|
try {
|
|
14241
14627
|
let result;
|
|
14628
|
+
const isFilePlugin = ref.isCurrentProject && ref.sourceType === "file-plugin";
|
|
14242
14629
|
if (noCache) {
|
|
14243
14630
|
result = await ctx.storage.fetch(ref, { branch });
|
|
14244
|
-
|
|
14631
|
+
if (!isFilePlugin) {
|
|
14632
|
+
await ctx.cache.set(uri, result);
|
|
14633
|
+
}
|
|
14245
14634
|
} else {
|
|
14246
14635
|
result = await ctx.cache.get(ref, { branch });
|
|
14247
14636
|
}
|
|
@@ -14249,6 +14638,10 @@ async function handleFetch(args, ctx) {
|
|
|
14249
14638
|
return resourceResult(uri, content, result.contentType);
|
|
14250
14639
|
} catch (error) {
|
|
14251
14640
|
const message = error instanceof Error ? error.message : String(error);
|
|
14641
|
+
const errorName = error instanceof Error ? error.name : "Error";
|
|
14642
|
+
if (errorName === "FilePluginFileNotFoundError") {
|
|
14643
|
+
return textResult(message, true);
|
|
14644
|
+
}
|
|
14252
14645
|
return textResult(`Failed to fetch ${uri}: ${message}`, true);
|
|
14253
14646
|
}
|
|
14254
14647
|
}
|
|
@@ -14347,6 +14740,33 @@ async function handleCacheClear(args, ctx) {
|
|
|
14347
14740
|
return textResult(`Failed to clear cache: ${message}`, true);
|
|
14348
14741
|
}
|
|
14349
14742
|
}
|
|
14743
|
+
async function handleFileSourcesList(ctx) {
|
|
14744
|
+
try {
|
|
14745
|
+
const filePluginProvider = ctx.storage.getProvider("file-plugin");
|
|
14746
|
+
if (!filePluginProvider) {
|
|
14747
|
+
return textResult(`No file plugin sources configured.
|
|
14748
|
+
|
|
14749
|
+
To enable file plugin integration, the storage manager must be initialized with filePlugin configuration.
|
|
14750
|
+
|
|
14751
|
+
File plugin sources allow codex to access current project artifacts (specs, logs, etc.) directly from the local filesystem without caching.`);
|
|
14752
|
+
}
|
|
14753
|
+
return textResult(`File plugin provider is configured.
|
|
14754
|
+
|
|
14755
|
+
File plugin sources are available for current project artifact access.
|
|
14756
|
+
Sources are read directly from local filesystem without caching.
|
|
14757
|
+
|
|
14758
|
+
To see available sources, check the unified configuration at:
|
|
14759
|
+
.fractary/config.yaml
|
|
14760
|
+
|
|
14761
|
+
Look for the 'file.sources' section which defines:
|
|
14762
|
+
- specs: .fractary/specs
|
|
14763
|
+
- logs: .fractary/logs
|
|
14764
|
+
- etc.`);
|
|
14765
|
+
} catch (error) {
|
|
14766
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
14767
|
+
return textResult(`Failed to list file sources: ${message}`, true);
|
|
14768
|
+
}
|
|
14769
|
+
}
|
|
14350
14770
|
async function handleToolCall(name, args, ctx) {
|
|
14351
14771
|
switch (name) {
|
|
14352
14772
|
case "codex_document_fetch":
|
|
@@ -14357,6 +14777,8 @@ async function handleToolCall(name, args, ctx) {
|
|
|
14357
14777
|
return handleList(args, ctx);
|
|
14358
14778
|
case "codex_cache_clear":
|
|
14359
14779
|
return handleCacheClear(args, ctx);
|
|
14780
|
+
case "codex_file_sources_list":
|
|
14781
|
+
return handleFileSourcesList(ctx);
|
|
14360
14782
|
default:
|
|
14361
14783
|
return textResult(`Unknown tool: ${name}`, true);
|
|
14362
14784
|
}
|