@fractary/codex 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/index.cjs +613 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +754 -48
- package/dist/index.d.ts +754 -48
- package/dist/index.js +595 -125
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
32
|
+
var path3__namespace = /*#__PURE__*/_interopNamespace(path3);
|
|
15
33
|
var yaml__default = /*#__PURE__*/_interopDefault(yaml);
|
|
16
|
-
var
|
|
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 =
|
|
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 =
|
|
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,
|
|
310
|
+
function buildUri(org, project, path7) {
|
|
293
311
|
const base = `${CODEX_URI_PREFIX}${org}/${project}`;
|
|
294
|
-
if (
|
|
295
|
-
const cleanPath =
|
|
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
|
|
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) :
|
|
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
|
|
454
|
+
return path3__namespace.default.join(parsed.org, parsed.project, parsed.path);
|
|
401
455
|
}
|
|
402
|
-
return
|
|
456
|
+
return path3__namespace.default.join(parsed.org, parsed.project);
|
|
403
457
|
}
|
|
404
458
|
|
|
405
459
|
// src/types/built-in.ts
|
|
@@ -854,8 +908,40 @@ var ArchiveProjectConfigSchema = zod.z.object({
|
|
|
854
908
|
var ArchiveConfigSchema = zod.z.object({
|
|
855
909
|
projects: zod.z.record(ArchiveProjectConfigSchema)
|
|
856
910
|
});
|
|
911
|
+
var GitHubAuthConfigSchema = zod.z.object({
|
|
912
|
+
/** Default token environment variable name (default: GITHUB_TOKEN) */
|
|
913
|
+
default_token_env: zod.z.string().optional(),
|
|
914
|
+
/** Fallback to public access if authentication fails */
|
|
915
|
+
fallback_to_public: zod.z.boolean().optional()
|
|
916
|
+
});
|
|
917
|
+
var AuthConfigSchema = zod.z.object({
|
|
918
|
+
/** GitHub authentication configuration */
|
|
919
|
+
github: GitHubAuthConfigSchema.optional()
|
|
920
|
+
});
|
|
921
|
+
var SourceConfigSchema = zod.z.object({
|
|
922
|
+
/** Source type */
|
|
923
|
+
type: zod.z.enum(["github", "s3", "http", "local"]),
|
|
924
|
+
/** Environment variable containing the authentication token */
|
|
925
|
+
token_env: zod.z.string().optional(),
|
|
926
|
+
/** Direct token value (not recommended, use token_env instead) */
|
|
927
|
+
token: zod.z.string().optional(),
|
|
928
|
+
/** Branch to fetch from (for GitHub sources) */
|
|
929
|
+
branch: zod.z.string().optional(),
|
|
930
|
+
/** Base URL (for HTTP sources) */
|
|
931
|
+
base_url: zod.z.string().optional(),
|
|
932
|
+
/** Bucket name (for S3 sources) */
|
|
933
|
+
bucket: zod.z.string().optional(),
|
|
934
|
+
/** Prefix/path within bucket (for S3 sources) */
|
|
935
|
+
prefix: zod.z.string().optional()
|
|
936
|
+
});
|
|
937
|
+
var DependencyConfigSchema = zod.z.object({
|
|
938
|
+
/** Sources within this dependency */
|
|
939
|
+
sources: zod.z.record(SourceConfigSchema)
|
|
940
|
+
});
|
|
857
941
|
var CodexConfigSchema = zod.z.object({
|
|
858
942
|
organizationSlug: zod.z.string(),
|
|
943
|
+
/** Project name (optional) */
|
|
944
|
+
project: zod.z.string().optional(),
|
|
859
945
|
directories: zod.z.object({
|
|
860
946
|
source: zod.z.string().optional(),
|
|
861
947
|
target: zod.z.string().optional(),
|
|
@@ -865,8 +951,47 @@ var CodexConfigSchema = zod.z.object({
|
|
|
865
951
|
// Directional sync configuration
|
|
866
952
|
sync: DirectionalSyncSchema.optional(),
|
|
867
953
|
// Archive configuration
|
|
868
|
-
archive: ArchiveConfigSchema.optional()
|
|
954
|
+
archive: ArchiveConfigSchema.optional(),
|
|
955
|
+
// Authentication configuration
|
|
956
|
+
auth: AuthConfigSchema.optional(),
|
|
957
|
+
// Dependencies configuration (external projects)
|
|
958
|
+
dependencies: zod.z.record(DependencyConfigSchema).optional()
|
|
869
959
|
}).strict();
|
|
960
|
+
var FileSourceSchema = zod.z.object({
|
|
961
|
+
type: zod.z.enum(["s3", "r2", "gcs", "local"]),
|
|
962
|
+
bucket: zod.z.string().optional(),
|
|
963
|
+
prefix: zod.z.string().optional(),
|
|
964
|
+
region: zod.z.string().optional(),
|
|
965
|
+
local: zod.z.object({
|
|
966
|
+
base_path: zod.z.string()
|
|
967
|
+
}),
|
|
968
|
+
push: zod.z.object({
|
|
969
|
+
compress: zod.z.boolean().optional(),
|
|
970
|
+
keep_local: zod.z.boolean().optional()
|
|
971
|
+
}).optional(),
|
|
972
|
+
auth: zod.z.object({
|
|
973
|
+
profile: zod.z.string().optional()
|
|
974
|
+
}).optional()
|
|
975
|
+
}).refine(
|
|
976
|
+
(data) => {
|
|
977
|
+
if (data.type !== "local" && !data.bucket) {
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
return true;
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
message: "Bucket is required for s3, r2, and gcs storage types",
|
|
984
|
+
path: ["bucket"]
|
|
985
|
+
}
|
|
986
|
+
);
|
|
987
|
+
var FileConfigSchema = zod.z.object({
|
|
988
|
+
schema_version: zod.z.string(),
|
|
989
|
+
sources: zod.z.record(FileSourceSchema)
|
|
990
|
+
});
|
|
991
|
+
zod.z.object({
|
|
992
|
+
file: FileConfigSchema.optional(),
|
|
993
|
+
codex: CodexConfigSchema.optional()
|
|
994
|
+
});
|
|
870
995
|
function parseMetadata(content, options = {}) {
|
|
871
996
|
const { strict = true, normalize = true } = options;
|
|
872
997
|
const normalizedContent = normalize ? content.replace(/\r\n/g, "\n") : content;
|
|
@@ -1196,18 +1321,18 @@ function parseCustomDestination(value) {
|
|
|
1196
1321
|
);
|
|
1197
1322
|
}
|
|
1198
1323
|
const repo = value.substring(0, colonIndex).trim();
|
|
1199
|
-
const
|
|
1324
|
+
const path7 = value.substring(colonIndex + 1).trim();
|
|
1200
1325
|
if (!repo) {
|
|
1201
1326
|
throw new ValidationError(
|
|
1202
1327
|
`Invalid custom destination: repository name cannot be empty in "${value}"`
|
|
1203
1328
|
);
|
|
1204
1329
|
}
|
|
1205
|
-
if (!
|
|
1330
|
+
if (!path7) {
|
|
1206
1331
|
throw new ValidationError(
|
|
1207
1332
|
`Invalid custom destination: path cannot be empty in "${value}"`
|
|
1208
1333
|
);
|
|
1209
1334
|
}
|
|
1210
|
-
return { repo, path:
|
|
1335
|
+
return { repo, path: path7 };
|
|
1211
1336
|
}
|
|
1212
1337
|
function getCustomSyncDestinations(metadata) {
|
|
1213
1338
|
const customDestinations = metadata.codex_sync_custom;
|
|
@@ -1243,8 +1368,8 @@ function mergeFetchOptions(options) {
|
|
|
1243
1368
|
...options
|
|
1244
1369
|
};
|
|
1245
1370
|
}
|
|
1246
|
-
function detectContentType(
|
|
1247
|
-
const ext =
|
|
1371
|
+
function detectContentType(path7) {
|
|
1372
|
+
const ext = path7.split(".").pop()?.toLowerCase();
|
|
1248
1373
|
const mimeTypes = {
|
|
1249
1374
|
md: "text/markdown",
|
|
1250
1375
|
markdown: "text/markdown",
|
|
@@ -1304,19 +1429,19 @@ var LocalStorage = class {
|
|
|
1304
1429
|
if (!reference.localPath) {
|
|
1305
1430
|
throw new Error(`No local path for reference: ${reference.uri}`);
|
|
1306
1431
|
}
|
|
1307
|
-
const fullPath =
|
|
1432
|
+
const fullPath = path3__namespace.default.isAbsolute(reference.localPath) ? reference.localPath : path3__namespace.default.join(this.baseDir, reference.localPath);
|
|
1308
1433
|
try {
|
|
1309
|
-
await
|
|
1434
|
+
await fs3__namespace.default.access(fullPath);
|
|
1310
1435
|
} catch {
|
|
1311
1436
|
throw new Error(`File not found: ${fullPath}`);
|
|
1312
1437
|
}
|
|
1313
|
-
const stats = await
|
|
1438
|
+
const stats = await fs3__namespace.default.stat(fullPath);
|
|
1314
1439
|
if (stats.size > opts.maxSize) {
|
|
1315
1440
|
throw new Error(
|
|
1316
1441
|
`File too large: ${stats.size} bytes (max: ${opts.maxSize} bytes)`
|
|
1317
1442
|
);
|
|
1318
1443
|
}
|
|
1319
|
-
const content = await
|
|
1444
|
+
const content = await fs3__namespace.default.readFile(fullPath);
|
|
1320
1445
|
return {
|
|
1321
1446
|
content,
|
|
1322
1447
|
contentType: detectContentType(reference.localPath),
|
|
@@ -1335,9 +1460,9 @@ var LocalStorage = class {
|
|
|
1335
1460
|
if (!reference.localPath) {
|
|
1336
1461
|
return false;
|
|
1337
1462
|
}
|
|
1338
|
-
const fullPath =
|
|
1463
|
+
const fullPath = path3__namespace.default.isAbsolute(reference.localPath) ? reference.localPath : path3__namespace.default.join(this.baseDir, reference.localPath);
|
|
1339
1464
|
try {
|
|
1340
|
-
await
|
|
1465
|
+
await fs3__namespace.default.access(fullPath);
|
|
1341
1466
|
return true;
|
|
1342
1467
|
} catch {
|
|
1343
1468
|
return false;
|
|
@@ -1347,24 +1472,24 @@ var LocalStorage = class {
|
|
|
1347
1472
|
* Read file content as string
|
|
1348
1473
|
*/
|
|
1349
1474
|
async readText(filePath) {
|
|
1350
|
-
const fullPath =
|
|
1351
|
-
return
|
|
1475
|
+
const fullPath = path3__namespace.default.isAbsolute(filePath) ? filePath : path3__namespace.default.join(this.baseDir, filePath);
|
|
1476
|
+
return fs3__namespace.default.readFile(fullPath, "utf-8");
|
|
1352
1477
|
}
|
|
1353
1478
|
/**
|
|
1354
1479
|
* Write content to file
|
|
1355
1480
|
*/
|
|
1356
1481
|
async write(filePath, content) {
|
|
1357
|
-
const fullPath =
|
|
1358
|
-
await
|
|
1359
|
-
await
|
|
1482
|
+
const fullPath = path3__namespace.default.isAbsolute(filePath) ? filePath : path3__namespace.default.join(this.baseDir, filePath);
|
|
1483
|
+
await fs3__namespace.default.mkdir(path3__namespace.default.dirname(fullPath), { recursive: true });
|
|
1484
|
+
await fs3__namespace.default.writeFile(fullPath, content);
|
|
1360
1485
|
}
|
|
1361
1486
|
/**
|
|
1362
1487
|
* Delete a file
|
|
1363
1488
|
*/
|
|
1364
1489
|
async delete(filePath) {
|
|
1365
|
-
const fullPath =
|
|
1490
|
+
const fullPath = path3__namespace.default.isAbsolute(filePath) ? filePath : path3__namespace.default.join(this.baseDir, filePath);
|
|
1366
1491
|
try {
|
|
1367
|
-
await
|
|
1492
|
+
await fs3__namespace.default.unlink(fullPath);
|
|
1368
1493
|
return true;
|
|
1369
1494
|
} catch {
|
|
1370
1495
|
return false;
|
|
@@ -1374,10 +1499,10 @@ var LocalStorage = class {
|
|
|
1374
1499
|
* List files in a directory
|
|
1375
1500
|
*/
|
|
1376
1501
|
async list(dirPath) {
|
|
1377
|
-
const fullPath =
|
|
1502
|
+
const fullPath = path3__namespace.default.isAbsolute(dirPath) ? dirPath : path3__namespace.default.join(this.baseDir, dirPath);
|
|
1378
1503
|
try {
|
|
1379
|
-
const entries = await
|
|
1380
|
-
return entries.filter((e) => e.isFile()).map((e) =>
|
|
1504
|
+
const entries = await fs3__namespace.default.readdir(fullPath, { withFileTypes: true });
|
|
1505
|
+
return entries.filter((e) => e.isFile()).map((e) => path3__namespace.default.join(dirPath, e.name));
|
|
1381
1506
|
} catch {
|
|
1382
1507
|
return [];
|
|
1383
1508
|
}
|
|
@@ -1722,6 +1847,300 @@ var HttpStorage = class {
|
|
|
1722
1847
|
function createHttpStorage(options) {
|
|
1723
1848
|
return new HttpStorage(options);
|
|
1724
1849
|
}
|
|
1850
|
+
|
|
1851
|
+
// src/file-integration/source-resolver.ts
|
|
1852
|
+
var FileSourceResolver = class {
|
|
1853
|
+
constructor(config) {
|
|
1854
|
+
this.config = config;
|
|
1855
|
+
this.initializeSources();
|
|
1856
|
+
}
|
|
1857
|
+
sources = /* @__PURE__ */ new Map();
|
|
1858
|
+
/**
|
|
1859
|
+
* Initialize sources from config
|
|
1860
|
+
*/
|
|
1861
|
+
initializeSources() {
|
|
1862
|
+
if (!this.config.file?.sources) {
|
|
1863
|
+
return;
|
|
1864
|
+
}
|
|
1865
|
+
for (const [name, sourceConfig] of Object.entries(this.config.file.sources)) {
|
|
1866
|
+
const resolved = {
|
|
1867
|
+
name,
|
|
1868
|
+
type: "file-plugin",
|
|
1869
|
+
localPath: sourceConfig.local.base_path,
|
|
1870
|
+
isCurrentProject: true,
|
|
1871
|
+
config: sourceConfig
|
|
1872
|
+
};
|
|
1873
|
+
if (sourceConfig.bucket && sourceConfig.type !== "local") {
|
|
1874
|
+
resolved.bucket = sourceConfig.bucket;
|
|
1875
|
+
resolved.prefix = sourceConfig.prefix;
|
|
1876
|
+
resolved.remotePath = this.buildRemotePath(sourceConfig);
|
|
1877
|
+
}
|
|
1878
|
+
this.sources.set(name, resolved);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Build remote path from source config
|
|
1883
|
+
*/
|
|
1884
|
+
buildRemotePath(source) {
|
|
1885
|
+
const protocol = source.type === "s3" ? "s3://" : source.type === "r2" ? "r2://" : "gcs://";
|
|
1886
|
+
const bucket = source.bucket;
|
|
1887
|
+
const prefix = source.prefix ? `/${source.prefix}` : "";
|
|
1888
|
+
return `${protocol}${bucket}${prefix}`;
|
|
1889
|
+
}
|
|
1890
|
+
/**
|
|
1891
|
+
* Get all available file plugin sources
|
|
1892
|
+
*
|
|
1893
|
+
* @returns Array of resolved file sources
|
|
1894
|
+
*/
|
|
1895
|
+
getAvailableSources() {
|
|
1896
|
+
return Array.from(this.sources.values());
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Resolve a source by name
|
|
1900
|
+
*
|
|
1901
|
+
* @param name - Source name (e.g., "specs", "logs")
|
|
1902
|
+
* @returns Resolved source or null if not found
|
|
1903
|
+
*/
|
|
1904
|
+
resolveSource(name) {
|
|
1905
|
+
return this.sources.get(name) || null;
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Check if a path belongs to any file plugin source
|
|
1909
|
+
*
|
|
1910
|
+
* @param path - File path to check
|
|
1911
|
+
* @returns True if path matches any source's base_path
|
|
1912
|
+
*/
|
|
1913
|
+
isFilePluginPath(path7) {
|
|
1914
|
+
return this.getSourceForPath(path7) !== null;
|
|
1915
|
+
}
|
|
1916
|
+
/**
|
|
1917
|
+
* Get the source for a given path
|
|
1918
|
+
*
|
|
1919
|
+
* Matches path against all source base_paths and returns the matching source.
|
|
1920
|
+
* Uses longest-match strategy if multiple sources match.
|
|
1921
|
+
*
|
|
1922
|
+
* @param path - File path to match
|
|
1923
|
+
* @returns Matching source or null
|
|
1924
|
+
*/
|
|
1925
|
+
getSourceForPath(path7) {
|
|
1926
|
+
let bestMatch = null;
|
|
1927
|
+
let bestMatchLength = 0;
|
|
1928
|
+
for (const source of this.sources.values()) {
|
|
1929
|
+
const normalizedPath = this.normalizePath(path7);
|
|
1930
|
+
const normalizedBasePath = this.normalizePath(source.localPath);
|
|
1931
|
+
if (normalizedPath.startsWith(normalizedBasePath)) {
|
|
1932
|
+
const matchLength = normalizedBasePath.length;
|
|
1933
|
+
if (matchLength > bestMatchLength) {
|
|
1934
|
+
bestMatch = source;
|
|
1935
|
+
bestMatchLength = matchLength;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
return bestMatch;
|
|
1940
|
+
}
|
|
1941
|
+
/**
|
|
1942
|
+
* Normalize path for comparison
|
|
1943
|
+
* - Remove leading "./" or "/"
|
|
1944
|
+
* - Remove trailing "/"
|
|
1945
|
+
* - Convert to lowercase for case-insensitive comparison
|
|
1946
|
+
*/
|
|
1947
|
+
normalizePath(path7) {
|
|
1948
|
+
return path7.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "").toLowerCase();
|
|
1949
|
+
}
|
|
1950
|
+
/**
|
|
1951
|
+
* Get source names
|
|
1952
|
+
*
|
|
1953
|
+
* @returns Array of source names
|
|
1954
|
+
*/
|
|
1955
|
+
getSourceNames() {
|
|
1956
|
+
return Array.from(this.sources.keys());
|
|
1957
|
+
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Check if sources are configured
|
|
1960
|
+
*
|
|
1961
|
+
* @returns True if any file plugin sources are configured
|
|
1962
|
+
*/
|
|
1963
|
+
hasSources() {
|
|
1964
|
+
return this.sources.size > 0;
|
|
1965
|
+
}
|
|
1966
|
+
};
|
|
1967
|
+
|
|
1968
|
+
// src/storage/errors.ts
|
|
1969
|
+
var FilePluginFileNotFoundError = class _FilePluginFileNotFoundError extends Error {
|
|
1970
|
+
constructor(filePath, sourceName, options) {
|
|
1971
|
+
const includeCloudSuggestions = options?.includeCloudSuggestions !== false;
|
|
1972
|
+
const storageType = options?.storageType;
|
|
1973
|
+
let message = `File not found: ${filePath}
|
|
1974
|
+
|
|
1975
|
+
`;
|
|
1976
|
+
if (includeCloudSuggestions) {
|
|
1977
|
+
if (storageType) {
|
|
1978
|
+
message += `This file may be in cloud storage (${storageType}).
|
|
1979
|
+
|
|
1980
|
+
`;
|
|
1981
|
+
} else {
|
|
1982
|
+
message += `This file may not have been synced from remote storage yet.
|
|
1983
|
+
`;
|
|
1984
|
+
}
|
|
1985
|
+
message += `To fetch from cloud storage, run:
|
|
1986
|
+
`;
|
|
1987
|
+
message += ` file pull ${sourceName}
|
|
1988
|
+
|
|
1989
|
+
`;
|
|
1990
|
+
message += `Or sync all sources:
|
|
1991
|
+
`;
|
|
1992
|
+
message += ` file sync`;
|
|
1993
|
+
} else {
|
|
1994
|
+
message += `Please ensure the file exists locally or pull it from cloud storage.`;
|
|
1995
|
+
}
|
|
1996
|
+
super(message);
|
|
1997
|
+
this.filePath = filePath;
|
|
1998
|
+
this.sourceName = sourceName;
|
|
1999
|
+
this.name = "FilePluginFileNotFoundError";
|
|
2000
|
+
if (Error.captureStackTrace) {
|
|
2001
|
+
Error.captureStackTrace(this, _FilePluginFileNotFoundError);
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
};
|
|
2005
|
+
|
|
2006
|
+
// src/storage/file-plugin.ts
|
|
2007
|
+
var FilePluginStorage = class {
|
|
2008
|
+
constructor(options) {
|
|
2009
|
+
this.options = options;
|
|
2010
|
+
this.sourceResolver = new FileSourceResolver(options.config);
|
|
2011
|
+
this.baseDir = options.baseDir || process.cwd();
|
|
2012
|
+
}
|
|
2013
|
+
name = "file-plugin";
|
|
2014
|
+
type = "local";
|
|
2015
|
+
// Reuse local type for compatibility
|
|
2016
|
+
sourceResolver;
|
|
2017
|
+
baseDir;
|
|
2018
|
+
/**
|
|
2019
|
+
* Check if this provider can handle the reference
|
|
2020
|
+
*
|
|
2021
|
+
* Only handles:
|
|
2022
|
+
* - Current project references
|
|
2023
|
+
* - With sourceType === 'file-plugin'
|
|
2024
|
+
*
|
|
2025
|
+
* @param reference - Resolved reference
|
|
2026
|
+
* @returns True if this provider can handle the reference
|
|
2027
|
+
*/
|
|
2028
|
+
canHandle(reference) {
|
|
2029
|
+
return reference.isCurrentProject && reference.sourceType === "file-plugin";
|
|
2030
|
+
}
|
|
2031
|
+
/**
|
|
2032
|
+
* Fetch content for a reference
|
|
2033
|
+
*
|
|
2034
|
+
* Reads from local filesystem based on the resolved local path.
|
|
2035
|
+
* If file not found and S3 fallback is enabled, throws helpful error.
|
|
2036
|
+
*
|
|
2037
|
+
* @param reference - Resolved reference
|
|
2038
|
+
* @param options - Fetch options (unused for local reads)
|
|
2039
|
+
* @returns Fetch result with content
|
|
2040
|
+
*/
|
|
2041
|
+
async fetch(reference, _options) {
|
|
2042
|
+
if (!reference.localPath) {
|
|
2043
|
+
throw new Error(`File plugin reference missing localPath: ${reference.uri}`);
|
|
2044
|
+
}
|
|
2045
|
+
if (!reference.filePluginSource) {
|
|
2046
|
+
throw new Error(`File plugin reference missing source name: ${reference.uri}`);
|
|
2047
|
+
}
|
|
2048
|
+
const absolutePath = path3__namespace.isAbsolute(reference.localPath) ? path3__namespace.resolve(reference.localPath) : path3__namespace.resolve(this.baseDir, reference.localPath);
|
|
2049
|
+
const source = this.sourceResolver.resolveSource(reference.filePluginSource);
|
|
2050
|
+
if (source) {
|
|
2051
|
+
const allowedDir = path3__namespace.resolve(this.baseDir, source.localPath);
|
|
2052
|
+
if (!absolutePath.startsWith(allowedDir + path3__namespace.sep) && absolutePath !== allowedDir) {
|
|
2053
|
+
throw new Error(
|
|
2054
|
+
`Path traversal detected: ${reference.localPath} resolves outside allowed directory ${source.localPath}`
|
|
2055
|
+
);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
try {
|
|
2059
|
+
const content = await fs3__namespace.readFile(absolutePath);
|
|
2060
|
+
const contentType = this.detectContentType(absolutePath);
|
|
2061
|
+
return {
|
|
2062
|
+
content,
|
|
2063
|
+
contentType,
|
|
2064
|
+
size: content.length,
|
|
2065
|
+
source: "file-plugin",
|
|
2066
|
+
metadata: {
|
|
2067
|
+
filePluginSource: reference.filePluginSource,
|
|
2068
|
+
localPath: absolutePath
|
|
2069
|
+
}
|
|
2070
|
+
};
|
|
2071
|
+
} catch (error) {
|
|
2072
|
+
if (error.code === "ENOENT") {
|
|
2073
|
+
const source2 = this.sourceResolver.resolveSource(reference.filePluginSource);
|
|
2074
|
+
throw this.createFileNotFoundError(reference, source2);
|
|
2075
|
+
}
|
|
2076
|
+
throw error;
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Check if a reference exists
|
|
2081
|
+
*
|
|
2082
|
+
* @param reference - Resolved reference
|
|
2083
|
+
* @param options - Fetch options (unused)
|
|
2084
|
+
* @returns True if file exists
|
|
2085
|
+
*/
|
|
2086
|
+
async exists(reference, _options) {
|
|
2087
|
+
if (!reference.localPath) {
|
|
2088
|
+
return false;
|
|
2089
|
+
}
|
|
2090
|
+
const absolutePath = path3__namespace.isAbsolute(reference.localPath) ? path3__namespace.resolve(reference.localPath) : path3__namespace.resolve(this.baseDir, reference.localPath);
|
|
2091
|
+
if (reference.filePluginSource) {
|
|
2092
|
+
const source = this.sourceResolver.resolveSource(reference.filePluginSource);
|
|
2093
|
+
if (source) {
|
|
2094
|
+
const allowedDir = path3__namespace.resolve(this.baseDir, source.localPath);
|
|
2095
|
+
if (!absolutePath.startsWith(allowedDir + path3__namespace.sep) && absolutePath !== allowedDir) {
|
|
2096
|
+
return false;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
try {
|
|
2101
|
+
await fs3__namespace.access(absolutePath);
|
|
2102
|
+
return true;
|
|
2103
|
+
} catch {
|
|
2104
|
+
return false;
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Detect content type from file extension
|
|
2109
|
+
*/
|
|
2110
|
+
detectContentType(filePath) {
|
|
2111
|
+
const ext = path3__namespace.extname(filePath).toLowerCase();
|
|
2112
|
+
const mimeTypes = {
|
|
2113
|
+
".md": "text/markdown",
|
|
2114
|
+
".txt": "text/plain",
|
|
2115
|
+
".json": "application/json",
|
|
2116
|
+
".yaml": "text/yaml",
|
|
2117
|
+
".yml": "text/yaml",
|
|
2118
|
+
".html": "text/html",
|
|
2119
|
+
".xml": "application/xml",
|
|
2120
|
+
".log": "text/plain",
|
|
2121
|
+
".js": "application/javascript",
|
|
2122
|
+
".ts": "application/typescript",
|
|
2123
|
+
".py": "text/x-python",
|
|
2124
|
+
".sh": "application/x-sh"
|
|
2125
|
+
};
|
|
2126
|
+
return mimeTypes[ext] || "application/octet-stream";
|
|
2127
|
+
}
|
|
2128
|
+
/**
|
|
2129
|
+
* Create a helpful error message when file is not found
|
|
2130
|
+
*/
|
|
2131
|
+
createFileNotFoundError(reference, source) {
|
|
2132
|
+
const includeCloudSuggestions = this.options.enableS3Fallback !== false;
|
|
2133
|
+
const storageType = source?.config.type;
|
|
2134
|
+
return new FilePluginFileNotFoundError(
|
|
2135
|
+
reference.localPath || reference.path || "",
|
|
2136
|
+
reference.filePluginSource || "",
|
|
2137
|
+
{
|
|
2138
|
+
includeCloudSuggestions,
|
|
2139
|
+
storageType
|
|
2140
|
+
}
|
|
2141
|
+
);
|
|
2142
|
+
}
|
|
2143
|
+
};
|
|
1725
2144
|
var execFileAsync = util.promisify(child_process.execFile);
|
|
1726
2145
|
async function execFileNoThrow(command, args = [], options) {
|
|
1727
2146
|
try {
|
|
@@ -1871,10 +2290,10 @@ var S3ArchiveStorage = class {
|
|
|
1871
2290
|
*
|
|
1872
2291
|
* Used to organize archives by type
|
|
1873
2292
|
*/
|
|
1874
|
-
detectType(
|
|
1875
|
-
if (
|
|
1876
|
-
if (
|
|
1877
|
-
if (
|
|
2293
|
+
detectType(path7) {
|
|
2294
|
+
if (path7.startsWith("specs/")) return "specs";
|
|
2295
|
+
if (path7.startsWith("docs/")) return "docs";
|
|
2296
|
+
if (path7.includes("/logs/")) return "logs";
|
|
1878
2297
|
return "misc";
|
|
1879
2298
|
}
|
|
1880
2299
|
/**
|
|
@@ -1885,9 +2304,9 @@ var S3ArchiveStorage = class {
|
|
|
1885
2304
|
* - *.md (all markdown files)
|
|
1886
2305
|
* - docs/*.md (markdown files in docs/)
|
|
1887
2306
|
*/
|
|
1888
|
-
matchesPatterns(
|
|
2307
|
+
matchesPatterns(path7, patterns) {
|
|
1889
2308
|
for (const pattern of patterns) {
|
|
1890
|
-
if (this.matchesPattern(
|
|
2309
|
+
if (this.matchesPattern(path7, pattern)) {
|
|
1891
2310
|
return true;
|
|
1892
2311
|
}
|
|
1893
2312
|
}
|
|
@@ -1896,14 +2315,14 @@ var S3ArchiveStorage = class {
|
|
|
1896
2315
|
/**
|
|
1897
2316
|
* Check if path matches a single pattern
|
|
1898
2317
|
*/
|
|
1899
|
-
matchesPattern(
|
|
2318
|
+
matchesPattern(path7, pattern) {
|
|
1900
2319
|
const DOUBLE_STAR = "\0DOUBLE_STAR\0";
|
|
1901
2320
|
let regexPattern = pattern.replace(/\*\*/g, DOUBLE_STAR);
|
|
1902
2321
|
regexPattern = regexPattern.replace(/[.[\](){}+^$|\\]/g, "\\$&");
|
|
1903
2322
|
regexPattern = regexPattern.replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
|
|
1904
2323
|
regexPattern = regexPattern.replace(new RegExp(DOUBLE_STAR, "g"), ".*");
|
|
1905
2324
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
1906
|
-
return regex.test(
|
|
2325
|
+
return regex.test(path7);
|
|
1907
2326
|
}
|
|
1908
2327
|
};
|
|
1909
2328
|
|
|
@@ -1911,14 +2330,61 @@ var S3ArchiveStorage = class {
|
|
|
1911
2330
|
var StorageManager = class {
|
|
1912
2331
|
providers = /* @__PURE__ */ new Map();
|
|
1913
2332
|
priority;
|
|
2333
|
+
codexConfig;
|
|
1914
2334
|
constructor(config = {}) {
|
|
2335
|
+
this.codexConfig = config.codexConfig;
|
|
1915
2336
|
this.providers.set("local", new LocalStorage(config.local));
|
|
1916
2337
|
this.providers.set("github", new GitHubStorage(config.github));
|
|
1917
2338
|
this.providers.set("http", new HttpStorage(config.http));
|
|
1918
2339
|
if (config.s3Archive) {
|
|
1919
2340
|
this.providers.set("s3-archive", new S3ArchiveStorage(config.s3Archive));
|
|
1920
2341
|
}
|
|
1921
|
-
|
|
2342
|
+
if (config.filePlugin) {
|
|
2343
|
+
this.providers.set("file-plugin", new FilePluginStorage(config.filePlugin));
|
|
2344
|
+
}
|
|
2345
|
+
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"]);
|
|
2346
|
+
}
|
|
2347
|
+
/**
|
|
2348
|
+
* Resolve authentication token for a reference
|
|
2349
|
+
*
|
|
2350
|
+
* Looks up dependency-specific authentication or falls back to default
|
|
2351
|
+
*/
|
|
2352
|
+
resolveToken(reference) {
|
|
2353
|
+
if (!this.codexConfig) {
|
|
2354
|
+
return void 0;
|
|
2355
|
+
}
|
|
2356
|
+
const dependencyKey = `${reference.org}/${reference.project}`;
|
|
2357
|
+
if (this.codexConfig.dependencies?.[dependencyKey]) {
|
|
2358
|
+
const dependency = this.codexConfig.dependencies[dependencyKey];
|
|
2359
|
+
for (const [, sourceConfig] of Object.entries(dependency.sources)) {
|
|
2360
|
+
if (sourceConfig.type === "github") {
|
|
2361
|
+
if (sourceConfig.token_env) {
|
|
2362
|
+
const token = process.env[sourceConfig.token_env];
|
|
2363
|
+
if (token) {
|
|
2364
|
+
return token;
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
if (sourceConfig.token) {
|
|
2368
|
+
return sourceConfig.token;
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
const defaultTokenEnv = this.codexConfig.auth?.github?.default_token_env || "GITHUB_TOKEN";
|
|
2374
|
+
return process.env[defaultTokenEnv];
|
|
2375
|
+
}
|
|
2376
|
+
/**
|
|
2377
|
+
* Resolve fetch options with authentication
|
|
2378
|
+
*
|
|
2379
|
+
* Merges reference-specific authentication with provided options
|
|
2380
|
+
*/
|
|
2381
|
+
resolveFetchOptions(reference, options) {
|
|
2382
|
+
const token = this.resolveToken(reference);
|
|
2383
|
+
return {
|
|
2384
|
+
...options,
|
|
2385
|
+
token: options?.token || token
|
|
2386
|
+
// Explicit option overrides resolved token
|
|
2387
|
+
};
|
|
1922
2388
|
}
|
|
1923
2389
|
/**
|
|
1924
2390
|
* Register a custom storage provider
|
|
@@ -1960,8 +2426,10 @@ var StorageManager = class {
|
|
|
1960
2426
|
* Fetch content for a reference
|
|
1961
2427
|
*
|
|
1962
2428
|
* Tries providers in priority order until one succeeds.
|
|
2429
|
+
* Automatically resolves authentication based on dependency configuration.
|
|
1963
2430
|
*/
|
|
1964
2431
|
async fetch(reference, options) {
|
|
2432
|
+
const resolvedOptions = this.resolveFetchOptions(reference, options);
|
|
1965
2433
|
const errors = [];
|
|
1966
2434
|
for (const type of this.priority) {
|
|
1967
2435
|
const provider = this.providers.get(type);
|
|
@@ -1969,7 +2437,7 @@ var StorageManager = class {
|
|
|
1969
2437
|
continue;
|
|
1970
2438
|
}
|
|
1971
2439
|
try {
|
|
1972
|
-
return await provider.fetch(reference,
|
|
2440
|
+
return await provider.fetch(reference, resolvedOptions);
|
|
1973
2441
|
} catch (error) {
|
|
1974
2442
|
errors.push(error instanceof Error ? error : new Error(String(error)));
|
|
1975
2443
|
}
|
|
@@ -1990,15 +2458,17 @@ var StorageManager = class {
|
|
|
1990
2458
|
* Check if content exists for a reference
|
|
1991
2459
|
*
|
|
1992
2460
|
* Returns true if any provider reports the content exists.
|
|
2461
|
+
* Automatically resolves authentication based on dependency configuration.
|
|
1993
2462
|
*/
|
|
1994
2463
|
async exists(reference, options) {
|
|
2464
|
+
const resolvedOptions = this.resolveFetchOptions(reference, options);
|
|
1995
2465
|
for (const type of this.priority) {
|
|
1996
2466
|
const provider = this.providers.get(type);
|
|
1997
2467
|
if (!provider || !provider.canHandle(reference)) {
|
|
1998
2468
|
continue;
|
|
1999
2469
|
}
|
|
2000
2470
|
try {
|
|
2001
|
-
if (await provider.exists(reference,
|
|
2471
|
+
if (await provider.exists(reference, resolvedOptions)) {
|
|
2002
2472
|
return true;
|
|
2003
2473
|
}
|
|
2004
2474
|
} catch {
|
|
@@ -2018,6 +2488,8 @@ var StorageManager = class {
|
|
|
2018
2488
|
}
|
|
2019
2489
|
/**
|
|
2020
2490
|
* Fetch multiple references in parallel
|
|
2491
|
+
*
|
|
2492
|
+
* Automatically resolves authentication for each reference based on dependency configuration.
|
|
2021
2493
|
*/
|
|
2022
2494
|
async fetchMany(references, options) {
|
|
2023
2495
|
const results = /* @__PURE__ */ new Map();
|
|
@@ -2158,7 +2630,7 @@ var CachePersistence = class {
|
|
|
2158
2630
|
}
|
|
2159
2631
|
const [, org, project, filePath] = match;
|
|
2160
2632
|
const relativePath = filePath || "index";
|
|
2161
|
-
return
|
|
2633
|
+
return path3__namespace.default.join(this.cacheDir, org, project, relativePath + this.extension);
|
|
2162
2634
|
}
|
|
2163
2635
|
/**
|
|
2164
2636
|
* Get the metadata file path for a URI
|
|
@@ -2174,8 +2646,8 @@ var CachePersistence = class {
|
|
|
2174
2646
|
const metadataPath = this.getMetadataPath(uri);
|
|
2175
2647
|
try {
|
|
2176
2648
|
const [metadataJson, content] = await Promise.all([
|
|
2177
|
-
|
|
2178
|
-
|
|
2649
|
+
fs3__namespace.default.readFile(metadataPath, "utf-8"),
|
|
2650
|
+
fs3__namespace.default.readFile(cachePath)
|
|
2179
2651
|
]);
|
|
2180
2652
|
const metadata = JSON.parse(metadataJson);
|
|
2181
2653
|
return {
|
|
@@ -2195,32 +2667,32 @@ var CachePersistence = class {
|
|
|
2195
2667
|
async write(entry) {
|
|
2196
2668
|
const cachePath = this.getCachePath(entry.metadata.uri);
|
|
2197
2669
|
const metadataPath = this.getMetadataPath(entry.metadata.uri);
|
|
2198
|
-
await
|
|
2670
|
+
await fs3__namespace.default.mkdir(path3__namespace.default.dirname(cachePath), { recursive: true });
|
|
2199
2671
|
if (this.atomicWrites) {
|
|
2200
2672
|
const tempCachePath = cachePath + ".tmp";
|
|
2201
2673
|
const tempMetadataPath = metadataPath + ".tmp";
|
|
2202
2674
|
try {
|
|
2203
2675
|
await Promise.all([
|
|
2204
|
-
|
|
2205
|
-
|
|
2676
|
+
fs3__namespace.default.writeFile(tempCachePath, entry.content),
|
|
2677
|
+
fs3__namespace.default.writeFile(tempMetadataPath, JSON.stringify(entry.metadata, null, 2))
|
|
2206
2678
|
]);
|
|
2207
2679
|
await Promise.all([
|
|
2208
|
-
|
|
2209
|
-
|
|
2680
|
+
fs3__namespace.default.rename(tempCachePath, cachePath),
|
|
2681
|
+
fs3__namespace.default.rename(tempMetadataPath, metadataPath)
|
|
2210
2682
|
]);
|
|
2211
2683
|
} catch (error) {
|
|
2212
2684
|
await Promise.all([
|
|
2213
|
-
|
|
2685
|
+
fs3__namespace.default.unlink(tempCachePath).catch(() => {
|
|
2214
2686
|
}),
|
|
2215
|
-
|
|
2687
|
+
fs3__namespace.default.unlink(tempMetadataPath).catch(() => {
|
|
2216
2688
|
})
|
|
2217
2689
|
]);
|
|
2218
2690
|
throw error;
|
|
2219
2691
|
}
|
|
2220
2692
|
} else {
|
|
2221
2693
|
await Promise.all([
|
|
2222
|
-
|
|
2223
|
-
|
|
2694
|
+
fs3__namespace.default.writeFile(cachePath, entry.content),
|
|
2695
|
+
fs3__namespace.default.writeFile(metadataPath, JSON.stringify(entry.metadata, null, 2))
|
|
2224
2696
|
]);
|
|
2225
2697
|
}
|
|
2226
2698
|
}
|
|
@@ -2231,7 +2703,7 @@ var CachePersistence = class {
|
|
|
2231
2703
|
const cachePath = this.getCachePath(uri);
|
|
2232
2704
|
const metadataPath = this.getMetadataPath(uri);
|
|
2233
2705
|
try {
|
|
2234
|
-
await Promise.all([
|
|
2706
|
+
await Promise.all([fs3__namespace.default.unlink(cachePath), fs3__namespace.default.unlink(metadataPath)]);
|
|
2235
2707
|
return true;
|
|
2236
2708
|
} catch (error) {
|
|
2237
2709
|
if (error.code === "ENOENT") {
|
|
@@ -2246,7 +2718,7 @@ var CachePersistence = class {
|
|
|
2246
2718
|
async exists(uri) {
|
|
2247
2719
|
const cachePath = this.getCachePath(uri);
|
|
2248
2720
|
try {
|
|
2249
|
-
await
|
|
2721
|
+
await fs3__namespace.default.access(cachePath);
|
|
2250
2722
|
return true;
|
|
2251
2723
|
} catch {
|
|
2252
2724
|
return false;
|
|
@@ -2258,20 +2730,20 @@ var CachePersistence = class {
|
|
|
2258
2730
|
async list() {
|
|
2259
2731
|
const uris = [];
|
|
2260
2732
|
try {
|
|
2261
|
-
const orgs = await
|
|
2733
|
+
const orgs = await fs3__namespace.default.readdir(this.cacheDir);
|
|
2262
2734
|
for (const org of orgs) {
|
|
2263
|
-
const orgPath =
|
|
2264
|
-
const orgStat = await
|
|
2735
|
+
const orgPath = path3__namespace.default.join(this.cacheDir, org);
|
|
2736
|
+
const orgStat = await fs3__namespace.default.stat(orgPath);
|
|
2265
2737
|
if (!orgStat.isDirectory()) continue;
|
|
2266
|
-
const projects = await
|
|
2738
|
+
const projects = await fs3__namespace.default.readdir(orgPath);
|
|
2267
2739
|
for (const project of projects) {
|
|
2268
|
-
const projectPath =
|
|
2269
|
-
const projectStat = await
|
|
2740
|
+
const projectPath = path3__namespace.default.join(orgPath, project);
|
|
2741
|
+
const projectStat = await fs3__namespace.default.stat(projectPath);
|
|
2270
2742
|
if (!projectStat.isDirectory()) continue;
|
|
2271
2743
|
const files = await this.listFilesRecursive(projectPath);
|
|
2272
2744
|
for (const file of files) {
|
|
2273
2745
|
if (file.endsWith(this.extension)) {
|
|
2274
|
-
const relativePath =
|
|
2746
|
+
const relativePath = path3__namespace.default.relative(projectPath, file);
|
|
2275
2747
|
const filePath = relativePath.slice(0, -this.extension.length);
|
|
2276
2748
|
uris.push(`codex://${org}/${project}/${filePath}`);
|
|
2277
2749
|
}
|
|
@@ -2291,9 +2763,9 @@ var CachePersistence = class {
|
|
|
2291
2763
|
*/
|
|
2292
2764
|
async listFilesRecursive(dir) {
|
|
2293
2765
|
const files = [];
|
|
2294
|
-
const entries = await
|
|
2766
|
+
const entries = await fs3__namespace.default.readdir(dir, { withFileTypes: true });
|
|
2295
2767
|
for (const entry of entries) {
|
|
2296
|
-
const fullPath =
|
|
2768
|
+
const fullPath = path3__namespace.default.join(dir, entry.name);
|
|
2297
2769
|
if (entry.isDirectory()) {
|
|
2298
2770
|
files.push(...await this.listFilesRecursive(fullPath));
|
|
2299
2771
|
} else if (entry.isFile()) {
|
|
@@ -2308,12 +2780,12 @@ var CachePersistence = class {
|
|
|
2308
2780
|
async clear() {
|
|
2309
2781
|
let count = 0;
|
|
2310
2782
|
try {
|
|
2311
|
-
const orgs = await
|
|
2783
|
+
const orgs = await fs3__namespace.default.readdir(this.cacheDir);
|
|
2312
2784
|
for (const org of orgs) {
|
|
2313
|
-
const orgPath =
|
|
2314
|
-
const stat = await
|
|
2785
|
+
const orgPath = path3__namespace.default.join(this.cacheDir, org);
|
|
2786
|
+
const stat = await fs3__namespace.default.stat(orgPath);
|
|
2315
2787
|
if (stat.isDirectory()) {
|
|
2316
|
-
await
|
|
2788
|
+
await fs3__namespace.default.rm(orgPath, { recursive: true });
|
|
2317
2789
|
count++;
|
|
2318
2790
|
}
|
|
2319
2791
|
}
|
|
@@ -2374,7 +2846,7 @@ var CachePersistence = class {
|
|
|
2374
2846
|
* Ensure cache directory exists
|
|
2375
2847
|
*/
|
|
2376
2848
|
async ensureDir() {
|
|
2377
|
-
await
|
|
2849
|
+
await fs3__namespace.default.mkdir(this.cacheDir, { recursive: true });
|
|
2378
2850
|
}
|
|
2379
2851
|
/**
|
|
2380
2852
|
* Get cache directory path
|
|
@@ -2422,8 +2894,17 @@ var CacheManager = class {
|
|
|
2422
2894
|
* Get content for a reference
|
|
2423
2895
|
*
|
|
2424
2896
|
* Implements cache-first strategy with stale-while-revalidate.
|
|
2897
|
+
*
|
|
2898
|
+
* EXCEPTION: File plugin sources (current project files) bypass cache entirely.
|
|
2899
|
+
* They are always read fresh from disk for optimal development experience.
|
|
2425
2900
|
*/
|
|
2426
2901
|
async get(reference, options) {
|
|
2902
|
+
if (reference.isCurrentProject && reference.sourceType === "file-plugin") {
|
|
2903
|
+
if (!this.storage) {
|
|
2904
|
+
throw new Error("Storage manager not set");
|
|
2905
|
+
}
|
|
2906
|
+
return await this.storage.fetch(reference, options);
|
|
2907
|
+
}
|
|
2427
2908
|
const ttl = options?.ttl ?? this.config.defaultTtl;
|
|
2428
2909
|
let entry = this.memoryCache.get(reference.uri);
|
|
2429
2910
|
if (!entry && this.persistence) {
|
|
@@ -2629,13 +3110,18 @@ var CacheManager = class {
|
|
|
2629
3110
|
}
|
|
2630
3111
|
/**
|
|
2631
3112
|
* Fetch content and store in cache
|
|
3113
|
+
*
|
|
3114
|
+
* EXCEPTION: File plugin sources are not cached (should not reach here,
|
|
3115
|
+
* but added as safety check).
|
|
2632
3116
|
*/
|
|
2633
3117
|
async fetchAndCache(reference, ttl, options) {
|
|
2634
3118
|
if (!this.storage) {
|
|
2635
3119
|
throw new Error("Storage manager not set");
|
|
2636
3120
|
}
|
|
2637
3121
|
const result = await this.storage.fetch(reference, options);
|
|
2638
|
-
|
|
3122
|
+
if (!(reference.isCurrentProject && reference.sourceType === "file-plugin")) {
|
|
3123
|
+
await this.set(reference.uri, result, ttl);
|
|
3124
|
+
}
|
|
2639
3125
|
return result;
|
|
2640
3126
|
}
|
|
2641
3127
|
/**
|
|
@@ -2754,11 +3240,11 @@ var DEFAULT_SYNC_CONFIG = {
|
|
|
2754
3240
|
deleteOrphans: false,
|
|
2755
3241
|
conflictStrategy: "newest"
|
|
2756
3242
|
};
|
|
2757
|
-
function evaluatePath(
|
|
3243
|
+
function evaluatePath(path7, rules, direction, defaultExcludes = []) {
|
|
2758
3244
|
for (const pattern of defaultExcludes) {
|
|
2759
|
-
if (micromatch3__default.default.isMatch(
|
|
3245
|
+
if (micromatch3__default.default.isMatch(path7, pattern)) {
|
|
2760
3246
|
return {
|
|
2761
|
-
path:
|
|
3247
|
+
path: path7,
|
|
2762
3248
|
shouldSync: false,
|
|
2763
3249
|
reason: `Excluded by default pattern: ${pattern}`
|
|
2764
3250
|
};
|
|
@@ -2769,9 +3255,9 @@ function evaluatePath(path6, rules, direction, defaultExcludes = []) {
|
|
|
2769
3255
|
if (rule.direction && rule.direction !== direction) {
|
|
2770
3256
|
continue;
|
|
2771
3257
|
}
|
|
2772
|
-
if (micromatch3__default.default.isMatch(
|
|
3258
|
+
if (micromatch3__default.default.isMatch(path7, rule.pattern)) {
|
|
2773
3259
|
return {
|
|
2774
|
-
path:
|
|
3260
|
+
path: path7,
|
|
2775
3261
|
shouldSync: rule.include,
|
|
2776
3262
|
matchedRule: rule,
|
|
2777
3263
|
reason: rule.include ? `Included by rule: ${rule.pattern}` : `Excluded by rule: ${rule.pattern}`
|
|
@@ -2779,21 +3265,21 @@ function evaluatePath(path6, rules, direction, defaultExcludes = []) {
|
|
|
2779
3265
|
}
|
|
2780
3266
|
}
|
|
2781
3267
|
return {
|
|
2782
|
-
path:
|
|
3268
|
+
path: path7,
|
|
2783
3269
|
shouldSync: true,
|
|
2784
3270
|
reason: "No matching rule, included by default"
|
|
2785
3271
|
};
|
|
2786
3272
|
}
|
|
2787
3273
|
function evaluatePaths(paths, rules, direction, defaultExcludes = []) {
|
|
2788
3274
|
const results = /* @__PURE__ */ new Map();
|
|
2789
|
-
for (const
|
|
2790
|
-
results.set(
|
|
3275
|
+
for (const path7 of paths) {
|
|
3276
|
+
results.set(path7, evaluatePath(path7, rules, direction, defaultExcludes));
|
|
2791
3277
|
}
|
|
2792
3278
|
return results;
|
|
2793
3279
|
}
|
|
2794
3280
|
function filterSyncablePaths(paths, rules, direction, defaultExcludes = []) {
|
|
2795
3281
|
return paths.filter(
|
|
2796
|
-
(
|
|
3282
|
+
(path7) => evaluatePath(path7, rules, direction, defaultExcludes).shouldSync
|
|
2797
3283
|
);
|
|
2798
3284
|
}
|
|
2799
3285
|
function createRulesFromPatterns(include = [], exclude = []) {
|
|
@@ -3059,8 +3545,8 @@ async function scanCodexWithRouting(options) {
|
|
|
3059
3545
|
for (const filePath of allFiles) {
|
|
3060
3546
|
totalScanned++;
|
|
3061
3547
|
try {
|
|
3062
|
-
const fullPath =
|
|
3063
|
-
const stats = await
|
|
3548
|
+
const fullPath = path3__namespace.default.join(codexDir, filePath);
|
|
3549
|
+
const stats = await fs3__namespace.default.stat(fullPath);
|
|
3064
3550
|
if (stats.size > maxFileSize) {
|
|
3065
3551
|
totalSkipped++;
|
|
3066
3552
|
errors.push({
|
|
@@ -3156,10 +3642,10 @@ async function listAllFilesRecursive(dirPath) {
|
|
|
3156
3642
|
const files = [];
|
|
3157
3643
|
async function scanDirectory(currentPath, relativePath = "") {
|
|
3158
3644
|
try {
|
|
3159
|
-
const entries = await
|
|
3645
|
+
const entries = await fs3__namespace.default.readdir(currentPath, { withFileTypes: true });
|
|
3160
3646
|
for (const entry of entries) {
|
|
3161
|
-
const entryPath =
|
|
3162
|
-
const entryRelativePath = relativePath ?
|
|
3647
|
+
const entryPath = path3__namespace.default.join(currentPath, entry.name);
|
|
3648
|
+
const entryRelativePath = relativePath ? path3__namespace.default.join(relativePath, entry.name) : entry.name;
|
|
3163
3649
|
if (entry.isDirectory()) {
|
|
3164
3650
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build") {
|
|
3165
3651
|
continue;
|
|
@@ -3398,17 +3884,17 @@ var SyncManager = class {
|
|
|
3398
3884
|
if (file.operation === "create" || file.operation === "update") {
|
|
3399
3885
|
const sourcePath = `${plan.source}/${file.path}`;
|
|
3400
3886
|
const targetPath = `${plan.target}/${file.path}`;
|
|
3401
|
-
const
|
|
3402
|
-
const
|
|
3403
|
-
const targetDir =
|
|
3404
|
-
await
|
|
3405
|
-
await
|
|
3887
|
+
const fs5 = await import('fs/promises');
|
|
3888
|
+
const path7 = await import('path');
|
|
3889
|
+
const targetDir = path7.dirname(targetPath);
|
|
3890
|
+
await fs5.mkdir(targetDir, { recursive: true });
|
|
3891
|
+
await fs5.copyFile(sourcePath, targetPath);
|
|
3406
3892
|
synced++;
|
|
3407
3893
|
} else if (file.operation === "delete") {
|
|
3408
3894
|
const targetPath = `${plan.target}/${file.path}`;
|
|
3409
|
-
const
|
|
3895
|
+
const fs5 = await import('fs/promises');
|
|
3410
3896
|
try {
|
|
3411
|
-
await
|
|
3897
|
+
await fs5.unlink(targetPath);
|
|
3412
3898
|
synced++;
|
|
3413
3899
|
} catch (error) {
|
|
3414
3900
|
if (error.code !== "ENOENT") {
|
|
@@ -3463,15 +3949,15 @@ var SyncManager = class {
|
|
|
3463
3949
|
/**
|
|
3464
3950
|
* Get sync status for a file
|
|
3465
3951
|
*/
|
|
3466
|
-
async getFileStatus(
|
|
3952
|
+
async getFileStatus(path7) {
|
|
3467
3953
|
const manifest = await this.loadManifest();
|
|
3468
|
-
return manifest?.entries[
|
|
3954
|
+
return manifest?.entries[path7] ?? null;
|
|
3469
3955
|
}
|
|
3470
3956
|
/**
|
|
3471
3957
|
* Check if a file is synced
|
|
3472
3958
|
*/
|
|
3473
|
-
async isFileSynced(
|
|
3474
|
-
const status = await this.getFileStatus(
|
|
3959
|
+
async isFileSynced(path7) {
|
|
3960
|
+
const status = await this.getFileStatus(path7);
|
|
3475
3961
|
return status !== null;
|
|
3476
3962
|
}
|
|
3477
3963
|
/**
|
|
@@ -3555,19 +4041,19 @@ function ruleMatchesContext(rule, context) {
|
|
|
3555
4041
|
}
|
|
3556
4042
|
return true;
|
|
3557
4043
|
}
|
|
3558
|
-
function ruleMatchesPath(rule,
|
|
3559
|
-
return micromatch3__default.default.isMatch(
|
|
4044
|
+
function ruleMatchesPath(rule, path7) {
|
|
4045
|
+
return micromatch3__default.default.isMatch(path7, rule.pattern);
|
|
3560
4046
|
}
|
|
3561
4047
|
function ruleMatchesAction(rule, action) {
|
|
3562
4048
|
return rule.actions.includes(action);
|
|
3563
4049
|
}
|
|
3564
|
-
function evaluatePermission(
|
|
4050
|
+
function evaluatePermission(path7, action, context, config) {
|
|
3565
4051
|
const sortedRules = [...config.rules].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
3566
4052
|
for (const rule of sortedRules) {
|
|
3567
4053
|
if (!ruleMatchesContext(rule, context)) {
|
|
3568
4054
|
continue;
|
|
3569
4055
|
}
|
|
3570
|
-
if (!ruleMatchesPath(rule,
|
|
4056
|
+
if (!ruleMatchesPath(rule, path7)) {
|
|
3571
4057
|
continue;
|
|
3572
4058
|
}
|
|
3573
4059
|
if (!ruleMatchesAction(rule, action)) {
|
|
@@ -3586,24 +4072,24 @@ function evaluatePermission(path6, action, context, config) {
|
|
|
3586
4072
|
reason: config.defaultAllow ? "Allowed by default" : "Denied by default"
|
|
3587
4073
|
};
|
|
3588
4074
|
}
|
|
3589
|
-
function isAllowed(
|
|
3590
|
-
const result = evaluatePermission(
|
|
4075
|
+
function isAllowed(path7, action, context, config) {
|
|
4076
|
+
const result = evaluatePermission(path7, action, context, config);
|
|
3591
4077
|
return result.allowed;
|
|
3592
4078
|
}
|
|
3593
|
-
function hasPermission(
|
|
3594
|
-
const result = evaluatePermission(
|
|
4079
|
+
function hasPermission(path7, action, requiredLevel, context, config) {
|
|
4080
|
+
const result = evaluatePermission(path7, action, context, config);
|
|
3595
4081
|
return levelGrants(result.level, requiredLevel);
|
|
3596
4082
|
}
|
|
3597
4083
|
function evaluatePermissions(paths, action, context, config) {
|
|
3598
4084
|
const results = /* @__PURE__ */ new Map();
|
|
3599
|
-
for (const
|
|
3600
|
-
results.set(
|
|
4085
|
+
for (const path7 of paths) {
|
|
4086
|
+
results.set(path7, evaluatePermission(path7, action, context, config));
|
|
3601
4087
|
}
|
|
3602
4088
|
return results;
|
|
3603
4089
|
}
|
|
3604
4090
|
function filterByPermission(paths, action, context, config, requiredLevel = "read") {
|
|
3605
|
-
return paths.filter((
|
|
3606
|
-
const result = evaluatePermission(
|
|
4091
|
+
return paths.filter((path7) => {
|
|
4092
|
+
const result = evaluatePermission(path7, action, context, config);
|
|
3607
4093
|
return levelGrants(result.level, requiredLevel);
|
|
3608
4094
|
});
|
|
3609
4095
|
}
|
|
@@ -3694,21 +4180,21 @@ var PermissionManager = class {
|
|
|
3694
4180
|
/**
|
|
3695
4181
|
* Check if an action is allowed for a path
|
|
3696
4182
|
*/
|
|
3697
|
-
isAllowed(
|
|
3698
|
-
const result = this.evaluate(
|
|
4183
|
+
isAllowed(path7, action, context) {
|
|
4184
|
+
const result = this.evaluate(path7, action, context);
|
|
3699
4185
|
return result.allowed;
|
|
3700
4186
|
}
|
|
3701
4187
|
/**
|
|
3702
4188
|
* Check if a permission level is granted
|
|
3703
4189
|
*/
|
|
3704
|
-
hasPermission(
|
|
3705
|
-
const result = this.evaluate(
|
|
4190
|
+
hasPermission(path7, action, requiredLevel, context) {
|
|
4191
|
+
const result = this.evaluate(path7, action, context);
|
|
3706
4192
|
return levelGrants(result.level, requiredLevel);
|
|
3707
4193
|
}
|
|
3708
4194
|
/**
|
|
3709
4195
|
* Evaluate permission for a path and action
|
|
3710
4196
|
*/
|
|
3711
|
-
evaluate(
|
|
4197
|
+
evaluate(path7, action, context) {
|
|
3712
4198
|
const mergedContext = { ...this.defaultContext, ...context };
|
|
3713
4199
|
if (!this.config.enforced) {
|
|
3714
4200
|
return {
|
|
@@ -3717,7 +4203,7 @@ var PermissionManager = class {
|
|
|
3717
4203
|
reason: "Permissions not enforced"
|
|
3718
4204
|
};
|
|
3719
4205
|
}
|
|
3720
|
-
return evaluatePermission(
|
|
4206
|
+
return evaluatePermission(path7, action, mergedContext, this.config);
|
|
3721
4207
|
}
|
|
3722
4208
|
/**
|
|
3723
4209
|
* Filter paths by permission
|
|
@@ -3820,12 +4306,12 @@ var PermissionManager = class {
|
|
|
3820
4306
|
/**
|
|
3821
4307
|
* Assert permission (throws if denied)
|
|
3822
4308
|
*/
|
|
3823
|
-
assertPermission(
|
|
3824
|
-
const result = this.evaluate(
|
|
4309
|
+
assertPermission(path7, action, requiredLevel = "read", context) {
|
|
4310
|
+
const result = this.evaluate(path7, action, context);
|
|
3825
4311
|
if (!levelGrants(result.level, requiredLevel)) {
|
|
3826
4312
|
throw new PermissionDeniedError(
|
|
3827
|
-
`Permission denied for ${action} on ${
|
|
3828
|
-
|
|
4313
|
+
`Permission denied for ${action} on ${path7}: ${result.reason}`,
|
|
4314
|
+
path7,
|
|
3829
4315
|
action,
|
|
3830
4316
|
result
|
|
3831
4317
|
);
|
|
@@ -3833,9 +4319,9 @@ var PermissionManager = class {
|
|
|
3833
4319
|
}
|
|
3834
4320
|
};
|
|
3835
4321
|
var PermissionDeniedError = class extends Error {
|
|
3836
|
-
constructor(message,
|
|
4322
|
+
constructor(message, path7, action, result) {
|
|
3837
4323
|
super(message);
|
|
3838
|
-
this.path =
|
|
4324
|
+
this.path = path7;
|
|
3839
4325
|
this.action = action;
|
|
3840
4326
|
this.result = result;
|
|
3841
4327
|
this.name = "PermissionDeniedError";
|
|
@@ -4332,8 +4818,8 @@ function convertToUri(reference, options = {}) {
|
|
|
4332
4818
|
const parts = parseReference2(trimmed);
|
|
4333
4819
|
const org = parts.org || options.defaultOrg || "_";
|
|
4334
4820
|
const project = parts.project || options.defaultProject || "_";
|
|
4335
|
-
const
|
|
4336
|
-
return `codex://${org}/${project}/${
|
|
4821
|
+
const path7 = parts.path;
|
|
4822
|
+
return `codex://${org}/${project}/${path7}`;
|
|
4337
4823
|
}
|
|
4338
4824
|
function parseReference2(reference) {
|
|
4339
4825
|
const trimmed = reference.trim();
|
|
@@ -4429,6 +4915,8 @@ exports.DEFAULT_MIGRATION_OPTIONS = DEFAULT_MIGRATION_OPTIONS;
|
|
|
4429
4915
|
exports.DEFAULT_PERMISSION_CONFIG = DEFAULT_PERMISSION_CONFIG;
|
|
4430
4916
|
exports.DEFAULT_SYNC_CONFIG = DEFAULT_SYNC_CONFIG;
|
|
4431
4917
|
exports.DEFAULT_TYPE = DEFAULT_TYPE;
|
|
4918
|
+
exports.FilePluginFileNotFoundError = FilePluginFileNotFoundError;
|
|
4919
|
+
exports.FilePluginStorage = FilePluginStorage;
|
|
4432
4920
|
exports.GitHubStorage = GitHubStorage;
|
|
4433
4921
|
exports.HttpStorage = HttpStorage;
|
|
4434
4922
|
exports.LEGACY_PATTERNS = LEGACY_PATTERNS;
|