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