@fractary/codex-cli 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,2246 @@
1
+ #!/usr/bin/env node
2
+ import * as path3 from 'path';
3
+ import 'url';
4
+ import * as fs from 'fs/promises';
5
+ import * as yaml from 'js-yaml';
6
+ import { ValidationError, PermissionDeniedError, ConfigurationError, CodexError } from '@fractary/codex';
7
+ import { Command } from 'commander';
8
+ import chalk6 from 'chalk';
9
+ import { execSync } from 'child_process';
10
+
11
+ var __defProp = Object.defineProperty;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
14
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
15
+ }) : x)(function(x) {
16
+ if (typeof require !== "undefined") return require.apply(this, arguments);
17
+ throw Error('Dynamic require of "' + x + '" is not supported');
18
+ });
19
+ var __esm = (fn, res) => function __init() {
20
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
21
+ };
22
+ var __export = (target, all) => {
23
+ for (var name in all)
24
+ __defProp(target, name, { get: all[name], enumerable: true });
25
+ };
26
+ var init_esm_shims = __esm({
27
+ "../node_modules/tsup/assets/esm_shims.js"() {
28
+ }
29
+ });
30
+
31
+ // src/config/migrate-config.ts
32
+ var migrate_config_exports = {};
33
+ __export(migrate_config_exports, {
34
+ getDefaultYamlConfig: () => getDefaultYamlConfig,
35
+ isLegacyConfig: () => isLegacyConfig,
36
+ migrateConfig: () => migrateConfig,
37
+ readYamlConfig: () => readYamlConfig,
38
+ writeYamlConfig: () => writeYamlConfig
39
+ });
40
+ async function isLegacyConfig(configPath) {
41
+ try {
42
+ const content = await fs.readFile(configPath, "utf-8");
43
+ const config = JSON.parse(content);
44
+ return config.version === "3.0" || config.organizationSlug !== void 0 || config.directories !== void 0 || config.rules !== void 0;
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+ async function migrateConfig(legacyConfigPath, options) {
50
+ const warnings = [];
51
+ try {
52
+ const content = await fs.readFile(legacyConfigPath, "utf-8");
53
+ const legacy = JSON.parse(content);
54
+ let backupPath;
55
+ if (options?.createBackup !== false) {
56
+ const suffix = options?.backupSuffix || (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
57
+ backupPath = `${legacyConfigPath}.backup-${suffix}`;
58
+ await fs.writeFile(backupPath, content, "utf-8");
59
+ }
60
+ const yamlConfig = {
61
+ organization: legacy.organization || legacy.organizationSlug || "default"
62
+ };
63
+ if (legacy.cache) {
64
+ yamlConfig.cacheDir = legacy.cache.directory || ".codex-cache";
65
+ }
66
+ if (legacy.storage?.providers) {
67
+ yamlConfig.storage = [];
68
+ for (const [type, config] of Object.entries(legacy.storage.providers)) {
69
+ if (type === "github") {
70
+ const githubConfig = config;
71
+ yamlConfig.storage.push({
72
+ type: "github",
73
+ token: githubConfig.token || "${GITHUB_TOKEN}",
74
+ apiBaseUrl: githubConfig.baseUrl || "https://api.github.com",
75
+ branch: githubConfig.branch || "main",
76
+ priority: 50
77
+ });
78
+ } else if (type === "http") {
79
+ const httpConfig = config;
80
+ yamlConfig.storage.push({
81
+ type: "http",
82
+ baseUrl: httpConfig.baseUrl,
83
+ headers: httpConfig.headers,
84
+ timeout: httpConfig.timeout || 3e4,
85
+ priority: 100
86
+ });
87
+ } else if (type === "local") {
88
+ const localConfig = config;
89
+ yamlConfig.storage.push({
90
+ type: "local",
91
+ basePath: localConfig.basePath || "./knowledge",
92
+ followSymlinks: localConfig.followSymlinks || false,
93
+ priority: 10
94
+ });
95
+ } else {
96
+ warnings.push(`Unknown storage provider type: ${type}`);
97
+ }
98
+ }
99
+ if (yamlConfig.storage.length === 0) {
100
+ yamlConfig.storage.push({
101
+ type: "github",
102
+ token: "${GITHUB_TOKEN}",
103
+ apiBaseUrl: "https://api.github.com",
104
+ branch: "main",
105
+ priority: 50
106
+ });
107
+ warnings.push("No storage providers found, added default GitHub provider");
108
+ }
109
+ }
110
+ if (legacy.types?.custom && Array.isArray(legacy.types.custom)) {
111
+ yamlConfig.types = {
112
+ custom: {}
113
+ };
114
+ for (const customType of legacy.types.custom) {
115
+ if (customType.name) {
116
+ yamlConfig.types.custom[customType.name] = {
117
+ description: customType.description,
118
+ patterns: customType.patterns || [],
119
+ defaultTtl: customType.defaultTtl,
120
+ archiveAfterDays: customType.archiveAfterDays,
121
+ archiveStorage: customType.archiveStorage
122
+ };
123
+ }
124
+ }
125
+ }
126
+ if (legacy.sync) {
127
+ yamlConfig.sync = {
128
+ bidirectional: true,
129
+ conflictResolution: "prompt",
130
+ exclude: [
131
+ "node_modules/**",
132
+ ".git/**",
133
+ "**/*.log",
134
+ ".env"
135
+ ]
136
+ };
137
+ if (legacy.sync.environments) {
138
+ warnings.push("Sync environments are not directly supported in v3.0 - please configure sync rules manually");
139
+ }
140
+ }
141
+ if (legacy.mcp) {
142
+ yamlConfig.mcp = {
143
+ enabled: legacy.mcp.enabled || false,
144
+ port: legacy.mcp.port || 3e3
145
+ };
146
+ }
147
+ return {
148
+ success: true,
149
+ yamlConfig,
150
+ warnings,
151
+ backupPath
152
+ };
153
+ } catch (error) {
154
+ throw new Error(
155
+ `Migration failed: ${error instanceof Error ? error.message : String(error)}`
156
+ );
157
+ }
158
+ }
159
+ async function writeYamlConfig(config, outputPath) {
160
+ const dir = path3.dirname(outputPath);
161
+ await fs.mkdir(dir, { recursive: true });
162
+ const yamlContent = yaml.dump(config, {
163
+ indent: 2,
164
+ lineWidth: 80,
165
+ noRefs: true,
166
+ sortKeys: false
167
+ });
168
+ await fs.writeFile(outputPath, yamlContent, "utf-8");
169
+ }
170
+ function getDefaultYamlConfig(organization) {
171
+ return {
172
+ organization,
173
+ cacheDir: ".codex-cache",
174
+ storage: [
175
+ {
176
+ type: "local",
177
+ basePath: "./knowledge",
178
+ followSymlinks: false,
179
+ priority: 10
180
+ },
181
+ {
182
+ type: "github",
183
+ token: "${GITHUB_TOKEN}",
184
+ apiBaseUrl: "https://api.github.com",
185
+ branch: "main",
186
+ priority: 50
187
+ },
188
+ {
189
+ type: "http",
190
+ baseUrl: "https://codex.example.com",
191
+ timeout: 3e4,
192
+ priority: 100
193
+ }
194
+ ],
195
+ types: {
196
+ custom: {}
197
+ },
198
+ permissions: {
199
+ default: "read",
200
+ rules: [
201
+ {
202
+ pattern: "internal/**",
203
+ permission: "none"
204
+ },
205
+ {
206
+ pattern: "public/**",
207
+ permission: "read"
208
+ }
209
+ ]
210
+ },
211
+ sync: {
212
+ bidirectional: true,
213
+ conflictResolution: "prompt",
214
+ exclude: [
215
+ "node_modules/**",
216
+ ".git/**",
217
+ "**/*.log",
218
+ ".env"
219
+ ]
220
+ },
221
+ mcp: {
222
+ enabled: false,
223
+ port: 3e3
224
+ }
225
+ };
226
+ }
227
+ async function readYamlConfig(configPath) {
228
+ const content = await fs.readFile(configPath, "utf-8");
229
+ const config = yaml.load(content);
230
+ if (!config.organization) {
231
+ throw new Error("Invalid config: organization is required");
232
+ }
233
+ return config;
234
+ }
235
+ var init_migrate_config = __esm({
236
+ "src/config/migrate-config.ts"() {
237
+ init_esm_shims();
238
+ }
239
+ });
240
+
241
+ // src/config/config-types.ts
242
+ var config_types_exports = {};
243
+ __export(config_types_exports, {
244
+ parseDuration: () => parseDuration,
245
+ parseSize: () => parseSize,
246
+ resolveEnvVars: () => resolveEnvVars,
247
+ resolveEnvVarsInConfig: () => resolveEnvVarsInConfig
248
+ });
249
+ function parseDuration(duration) {
250
+ if (typeof duration === "number") {
251
+ return duration;
252
+ }
253
+ const match = duration.match(/^(\d+)([smhdwMy])$/);
254
+ if (!match) {
255
+ throw new Error(`Invalid duration format: ${duration}`);
256
+ }
257
+ const [, valueStr, unit] = match;
258
+ const value = parseInt(valueStr, 10);
259
+ switch (unit) {
260
+ case "s":
261
+ return value;
262
+ case "m":
263
+ return value * 60;
264
+ case "h":
265
+ return value * 3600;
266
+ case "d":
267
+ return value * 86400;
268
+ case "w":
269
+ return value * 604800;
270
+ case "M":
271
+ return value * 2592e3;
272
+ // 30 days
273
+ case "y":
274
+ return value * 31536e3;
275
+ // 365 days
276
+ default:
277
+ throw new Error(`Unknown duration unit: ${unit}`);
278
+ }
279
+ }
280
+ function parseSize(size) {
281
+ if (typeof size === "number") {
282
+ return size;
283
+ }
284
+ const match = size.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB)$/i);
285
+ if (!match) {
286
+ throw new Error(`Invalid size format: ${size}`);
287
+ }
288
+ const [, valueStr, unit] = match;
289
+ const value = parseFloat(valueStr);
290
+ switch (unit.toUpperCase()) {
291
+ case "B":
292
+ return value;
293
+ case "KB":
294
+ return value * 1024;
295
+ case "MB":
296
+ return value * 1024 * 1024;
297
+ case "GB":
298
+ return value * 1024 * 1024 * 1024;
299
+ default:
300
+ throw new Error(`Unknown size unit: ${unit}`);
301
+ }
302
+ }
303
+ function resolveEnvVars(value) {
304
+ return value.replace(/\$\{([^}]+)\}/g, (_, varName) => {
305
+ const envValue = process.env[varName];
306
+ if (envValue === void 0) {
307
+ console.warn(`Warning: Environment variable ${varName} is not set`);
308
+ return `\${${varName}}`;
309
+ }
310
+ return envValue;
311
+ });
312
+ }
313
+ function resolveEnvVarsInConfig(config) {
314
+ if (typeof config === "string") {
315
+ return resolveEnvVars(config);
316
+ }
317
+ if (Array.isArray(config)) {
318
+ return config.map((item) => resolveEnvVarsInConfig(item));
319
+ }
320
+ if (config !== null && typeof config === "object") {
321
+ const result = {};
322
+ for (const [key, value] of Object.entries(config)) {
323
+ result[key] = resolveEnvVarsInConfig(value);
324
+ }
325
+ return result;
326
+ }
327
+ return config;
328
+ }
329
+ var init_config_types = __esm({
330
+ "src/config/config-types.ts"() {
331
+ init_esm_shims();
332
+ }
333
+ });
334
+
335
+ // src/client/codex-client.ts
336
+ var codex_client_exports = {};
337
+ __export(codex_client_exports, {
338
+ CodexClient: () => CodexClient,
339
+ CodexError: () => CodexError,
340
+ ConfigurationError: () => ConfigurationError,
341
+ PermissionDeniedError: () => PermissionDeniedError,
342
+ ValidationError: () => ValidationError
343
+ });
344
+ var CodexClient;
345
+ var init_codex_client = __esm({
346
+ "src/client/codex-client.ts"() {
347
+ init_esm_shims();
348
+ CodexClient = class _CodexClient {
349
+ cache;
350
+ storage;
351
+ types;
352
+ organization;
353
+ /**
354
+ * Private constructor - use CodexClient.create() instead
355
+ */
356
+ constructor(cache, storage, types, organization) {
357
+ this.cache = cache;
358
+ this.storage = storage;
359
+ this.types = types;
360
+ this.organization = organization;
361
+ }
362
+ /**
363
+ * Create a new CodexClient instance
364
+ *
365
+ * @param options - Optional configuration
366
+ * @returns Promise resolving to CodexClient instance
367
+ *
368
+ * @example
369
+ * ```typescript
370
+ * const client = await CodexClient.create();
371
+ * ```
372
+ */
373
+ static async create(options) {
374
+ const {
375
+ CacheManager,
376
+ createStorageManager,
377
+ createDefaultRegistry,
378
+ CodexError: CodexError2,
379
+ ConfigurationError: ConfigurationError2
380
+ } = await import('@fractary/codex');
381
+ const { readYamlConfig: readYamlConfig2 } = await Promise.resolve().then(() => (init_migrate_config(), migrate_config_exports));
382
+ const { resolveEnvVarsInConfig: resolveEnvVarsInConfig2 } = await Promise.resolve().then(() => (init_config_types(), config_types_exports));
383
+ try {
384
+ const configPath = path3.join(process.cwd(), ".fractary", "codex.yaml");
385
+ let config;
386
+ try {
387
+ config = await readYamlConfig2(configPath);
388
+ config = resolveEnvVarsInConfig2(config);
389
+ } catch (error) {
390
+ throw new ConfigurationError2(
391
+ `Failed to load configuration from ${configPath}. Run "fractary codex init" to create a configuration.`
392
+ );
393
+ }
394
+ const organization = options?.organizationSlug || config.organization;
395
+ const cacheDir = options?.cacheDir || config.cacheDir || ".codex-cache";
396
+ const storageConfig = {};
397
+ if (config.storage && Array.isArray(config.storage)) {
398
+ for (const provider of config.storage) {
399
+ if (provider.type === "github") {
400
+ storageConfig.github = {
401
+ token: provider.token || process.env.GITHUB_TOKEN,
402
+ apiBaseUrl: provider.apiBaseUrl || "https://api.github.com",
403
+ branch: provider.branch || "main"
404
+ };
405
+ } else if (provider.type === "http") {
406
+ storageConfig.http = {
407
+ baseUrl: provider.baseUrl,
408
+ headers: provider.headers,
409
+ timeout: provider.timeout || 3e4
410
+ };
411
+ } else if (provider.type === "local") {
412
+ storageConfig.local = {
413
+ basePath: provider.basePath || "./knowledge",
414
+ followSymlinks: provider.followSymlinks || false
415
+ };
416
+ }
417
+ }
418
+ }
419
+ const storage = createStorageManager(storageConfig);
420
+ const cache = new CacheManager({
421
+ cacheDir,
422
+ defaultTtl: 86400,
423
+ // 24 hours
424
+ maxMemoryEntries: 100,
425
+ maxMemorySize: 50 * 1024 * 1024,
426
+ // 50MB
427
+ enablePersistence: true
428
+ });
429
+ cache.setStorageManager(storage);
430
+ const types = createDefaultRegistry();
431
+ if (config.types?.custom) {
432
+ for (const [name, customType] of Object.entries(config.types.custom)) {
433
+ const ct = customType;
434
+ types.register({
435
+ name,
436
+ description: ct.description || `Custom type: ${name}`,
437
+ patterns: ct.patterns || [],
438
+ defaultTtl: ct.defaultTtl || 86400,
439
+ archiveAfterDays: ct.archiveAfterDays !== void 0 ? ct.archiveAfterDays : null,
440
+ archiveStorage: ct.archiveStorage || null
441
+ });
442
+ }
443
+ }
444
+ return new _CodexClient(cache, storage, types, organization);
445
+ } catch (error) {
446
+ if (error instanceof CodexError2) {
447
+ throw error;
448
+ }
449
+ throw new CodexError2(
450
+ `Failed to initialize CodexClient: ${error instanceof Error ? error.message : String(error)}`
451
+ );
452
+ }
453
+ }
454
+ /**
455
+ * Fetch a document by codex:// URI
456
+ *
457
+ * This method:
458
+ * 1. Validates the URI format
459
+ * 2. Resolves the URI to a reference
460
+ * 3. Uses CacheManager.get() which handles cache-first fetch
461
+ *
462
+ * @param uri - Codex URI (e.g., codex://org/project/path/to/file.md)
463
+ * @param options - Fetch options
464
+ * @returns Promise resolving to fetch result
465
+ *
466
+ * @throws {CodexError} If URI format is invalid or fetch fails
467
+ *
468
+ * @example
469
+ * ```typescript
470
+ * const result = await client.fetch('codex://fractary/codex/docs/README.md');
471
+ * console.log(result.content.toString());
472
+ * ```
473
+ */
474
+ async fetch(uri, options) {
475
+ const { validateUri, resolveReference, CodexError: CodexError2 } = await import('@fractary/codex');
476
+ if (!validateUri(uri)) {
477
+ throw new CodexError2(`Invalid codex URI: ${uri}`);
478
+ }
479
+ const resolved = resolveReference(uri);
480
+ if (!resolved) {
481
+ throw new CodexError2(`Failed to resolve URI: ${uri}`);
482
+ }
483
+ try {
484
+ if (options?.bypassCache) {
485
+ const result2 = await this.storage.fetch(resolved);
486
+ return {
487
+ content: result2.content,
488
+ fromCache: false,
489
+ metadata: {
490
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
491
+ contentLength: result2.size
492
+ }
493
+ };
494
+ }
495
+ const result = await this.cache.get(resolved, {
496
+ ttl: options?.ttl
497
+ });
498
+ return {
499
+ content: result.content,
500
+ fromCache: true,
501
+ // CacheManager.get handles cache logic
502
+ metadata: {
503
+ contentLength: result.size
504
+ }
505
+ };
506
+ } catch (error) {
507
+ throw new CodexError2(
508
+ `Failed to fetch ${uri}: ${error instanceof Error ? error.message : String(error)}`
509
+ );
510
+ }
511
+ }
512
+ /**
513
+ * Invalidate cache entries
514
+ *
515
+ * @param pattern - Optional glob pattern to match entries
516
+ * If not provided, clears all entries
517
+ *
518
+ * @example
519
+ * ```typescript
520
+ * // Clear all cache
521
+ * await client.invalidateCache();
522
+ *
523
+ * // Clear specific URI
524
+ * await client.invalidateCache('codex://fractary/codex/docs/README.md');
525
+ * ```
526
+ */
527
+ async invalidateCache(pattern) {
528
+ if (pattern) {
529
+ await this.cache.invalidate(pattern);
530
+ } else {
531
+ await this.cache.clear();
532
+ }
533
+ }
534
+ /**
535
+ * Get cache statistics
536
+ *
537
+ * @returns Promise resolving to cache stats
538
+ *
539
+ * @example
540
+ * ```typescript
541
+ * const stats = await client.getCacheStats();
542
+ * console.log(`Cache entries: ${stats.totalEntries}`);
543
+ * console.log(`Total size: ${stats.totalSize}`);
544
+ * ```
545
+ */
546
+ async getCacheStats() {
547
+ return this.cache.getStats();
548
+ }
549
+ /**
550
+ * Get the type registry
551
+ *
552
+ * Provides access to built-in and custom artifact types
553
+ *
554
+ * @returns TypeRegistry instance
555
+ *
556
+ * @example
557
+ * ```typescript
558
+ * const registry = client.getTypeRegistry();
559
+ * const types = registry.list();
560
+ * ```
561
+ */
562
+ getTypeRegistry() {
563
+ return this.types;
564
+ }
565
+ /**
566
+ * Get the cache manager (for advanced operations)
567
+ *
568
+ * @returns CacheManager instance
569
+ */
570
+ getCacheManager() {
571
+ return this.cache;
572
+ }
573
+ /**
574
+ * Get the storage manager (for advanced operations)
575
+ *
576
+ * @returns StorageManager instance
577
+ */
578
+ getStorageManager() {
579
+ return this.storage;
580
+ }
581
+ /**
582
+ * Get the organization slug
583
+ *
584
+ * @returns Organization slug string
585
+ */
586
+ getOrganization() {
587
+ return this.organization;
588
+ }
589
+ };
590
+ }
591
+ });
592
+
593
+ // src/cli.ts
594
+ init_esm_shims();
595
+
596
+ // src/commands/init.ts
597
+ init_esm_shims();
598
+ init_migrate_config();
599
+ async function getOrgFromGitRemote() {
600
+ try {
601
+ const { execSync: execSync2 } = __require("child_process");
602
+ const remote = execSync2("git remote get-url origin 2>/dev/null", { encoding: "utf-8" }).trim();
603
+ const sshMatch = remote.match(/git@github\.com:([^/]+)\//);
604
+ const httpsMatch = remote.match(/github\.com\/([^/]+)\//);
605
+ return sshMatch?.[1] || httpsMatch?.[1] || null;
606
+ } catch {
607
+ return null;
608
+ }
609
+ }
610
+ async function fileExists(filePath) {
611
+ try {
612
+ await fs.access(filePath);
613
+ return true;
614
+ } catch {
615
+ return false;
616
+ }
617
+ }
618
+ function initCommand() {
619
+ const cmd = new Command("init");
620
+ cmd.description("Initialize Codex v3.0 with YAML configuration").option("--org <slug>", 'Organization slug (e.g., "fractary")').option("--mcp", "Enable MCP server registration").option("--force", "Overwrite existing configuration").action(async (options) => {
621
+ try {
622
+ console.log(chalk6.blue("Initializing Codex v3.0 (YAML format)...\n"));
623
+ let org = options.org;
624
+ if (!org) {
625
+ org = await getOrgFromGitRemote();
626
+ }
627
+ if (!org) {
628
+ try {
629
+ const { resolveOrganization } = await import('@fractary/codex');
630
+ org = resolveOrganization({
631
+ repoName: path3.basename(process.cwd())
632
+ });
633
+ } catch {
634
+ }
635
+ }
636
+ if (!org) {
637
+ org = path3.basename(process.cwd()).split("-")[0] || "default";
638
+ console.log(chalk6.yellow(`\u26A0 Could not detect organization, using: ${org}`));
639
+ console.log(chalk6.dim(" Use --org <slug> to specify explicitly\n"));
640
+ } else {
641
+ console.log(chalk6.dim(`Organization: ${chalk6.cyan(org)}
642
+ `));
643
+ }
644
+ const configDir = path3.join(process.cwd(), ".fractary");
645
+ const configPath = path3.join(configDir, "codex.yaml");
646
+ const configExists = await fileExists(configPath);
647
+ const legacyConfigPath = path3.join(process.cwd(), ".fractary", "plugins", "codex", "config.json");
648
+ const legacyExists = await fileExists(legacyConfigPath);
649
+ if (configExists && !options.force) {
650
+ console.log(chalk6.yellow("\u26A0 Configuration already exists at .fractary/codex.yaml"));
651
+ console.log(chalk6.dim("Use --force to overwrite"));
652
+ process.exit(1);
653
+ }
654
+ if (legacyExists && !configExists) {
655
+ console.log(chalk6.yellow("\u26A0 Legacy configuration detected at .fractary/plugins/codex/config.json"));
656
+ console.log(chalk6.dim('Run "fractary codex migrate" to upgrade to YAML format\n'));
657
+ }
658
+ console.log("Creating directory structure...");
659
+ const dirs = [
660
+ ".fractary",
661
+ ".codex-cache"
662
+ ];
663
+ for (const dir of dirs) {
664
+ await fs.mkdir(path3.join(process.cwd(), dir), { recursive: true });
665
+ console.log(chalk6.green("\u2713"), chalk6.dim(dir + "/"));
666
+ }
667
+ console.log("\nCreating YAML configuration...");
668
+ const config = getDefaultYamlConfig(org);
669
+ if (options.mcp && config.mcp) {
670
+ config.mcp.enabled = true;
671
+ }
672
+ await writeYamlConfig(config, configPath);
673
+ console.log(chalk6.green("\u2713"), chalk6.dim(".fractary/codex.yaml"));
674
+ console.log(chalk6.green("\n\u2713 Codex v3.0 initialized successfully!\n"));
675
+ console.log(chalk6.bold("Configuration:"));
676
+ console.log(chalk6.dim(` Organization: ${org}`));
677
+ console.log(chalk6.dim(` Cache: .codex-cache/`));
678
+ console.log(chalk6.dim(` Config: .fractary/codex.yaml`));
679
+ if (options.mcp) {
680
+ console.log(chalk6.dim(` MCP Server: Enabled (port 3000)`));
681
+ }
682
+ console.log(chalk6.bold("\nStorage providers configured:"));
683
+ console.log(chalk6.dim(" - Local filesystem (./knowledge)"));
684
+ console.log(chalk6.dim(" - GitHub (requires GITHUB_TOKEN)"));
685
+ console.log(chalk6.dim(" - HTTP endpoint"));
686
+ console.log(chalk6.bold("\nNext steps:"));
687
+ console.log(chalk6.dim(' 1. Set your GitHub token: export GITHUB_TOKEN="your_token"'));
688
+ console.log(chalk6.dim(" 2. Edit .fractary/codex.yaml to configure storage providers"));
689
+ console.log(chalk6.dim(" 3. Fetch a document: fractary codex fetch codex://org/project/path"));
690
+ console.log(chalk6.dim(" 4. Check cache: fractary codex cache list"));
691
+ if (legacyExists) {
692
+ console.log(chalk6.yellow("\n\u26A0 Legacy config detected:"));
693
+ console.log(chalk6.dim(' Run "fractary codex migrate" to convert your existing config'));
694
+ }
695
+ } catch (error) {
696
+ console.error(chalk6.red("Error:"), error.message);
697
+ process.exit(1);
698
+ }
699
+ });
700
+ return cmd;
701
+ }
702
+
703
+ // src/commands/fetch.ts
704
+ init_esm_shims();
705
+
706
+ // src/client/get-client.ts
707
+ init_esm_shims();
708
+ var clientInstance = null;
709
+ async function getClient(options) {
710
+ if (!clientInstance) {
711
+ const { CodexClient: CodexClient2 } = await Promise.resolve().then(() => (init_codex_client(), codex_client_exports));
712
+ clientInstance = await CodexClient2.create(options);
713
+ }
714
+ return clientInstance;
715
+ }
716
+
717
+ // src/commands/fetch.ts
718
+ function hashContent(content) {
719
+ const crypto = __require("crypto");
720
+ return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
721
+ }
722
+ function fetchCommand() {
723
+ const cmd = new Command("fetch");
724
+ cmd.description("Fetch a document by codex:// URI reference").argument("<uri>", "Codex URI (e.g., codex://org/project/docs/file.md)").option("--bypass-cache", "Skip cache and fetch directly from source").option("--ttl <seconds>", "Override default TTL (in seconds)", parseInt).option("--json", "Output as JSON with metadata").option("--output <file>", "Write content to file instead of stdout").action(async (uri, options) => {
725
+ try {
726
+ const { validateUri } = await import('@fractary/codex');
727
+ if (!validateUri(uri)) {
728
+ console.error(chalk6.red("Error: Invalid URI format"));
729
+ console.log(chalk6.dim("Expected: codex://org/project/path/to/file.md"));
730
+ console.log(chalk6.dim("Example: codex://fractary/codex/docs/api.md"));
731
+ process.exit(1);
732
+ }
733
+ const client = await getClient();
734
+ if (!options.json && !options.bypassCache) {
735
+ console.error(chalk6.dim(`Fetching ${uri}...`));
736
+ }
737
+ const result = await client.fetch(uri, {
738
+ bypassCache: options.bypassCache,
739
+ ttl: options.ttl
740
+ });
741
+ if (options.json) {
742
+ const output = {
743
+ uri,
744
+ content: result.content.toString("utf-8"),
745
+ metadata: {
746
+ fromCache: result.fromCache,
747
+ fetchedAt: result.metadata?.fetchedAt,
748
+ expiresAt: result.metadata?.expiresAt,
749
+ contentLength: result.metadata?.contentLength || result.content.length,
750
+ contentHash: hashContent(result.content)
751
+ }
752
+ };
753
+ console.log(JSON.stringify(output, null, 2));
754
+ } else if (options.output) {
755
+ await fs.writeFile(options.output, result.content);
756
+ console.log(chalk6.green("\u2713"), `Written to ${options.output}`);
757
+ console.log(chalk6.dim(` Size: ${result.content.length} bytes`));
758
+ if (result.fromCache) {
759
+ console.log(chalk6.dim(" Source: cache"));
760
+ } else {
761
+ console.log(chalk6.dim(" Source: storage"));
762
+ }
763
+ } else {
764
+ if (result.fromCache && !options.bypassCache) {
765
+ console.error(chalk6.green("\u2713"), chalk6.dim("from cache\n"));
766
+ } else {
767
+ console.error(chalk6.green("\u2713"), chalk6.dim("fetched\n"));
768
+ }
769
+ console.log(result.content.toString("utf-8"));
770
+ }
771
+ } catch (error) {
772
+ console.error(chalk6.red("Error:"), error.message);
773
+ if (error.message.includes("Failed to load configuration")) {
774
+ console.log(chalk6.dim('\nRun "fractary codex init" to create a configuration.'));
775
+ } else if (error.message.includes("GITHUB_TOKEN")) {
776
+ console.log(chalk6.dim('\nSet your GitHub token: export GITHUB_TOKEN="your_token"'));
777
+ } else if (error.message.includes("not found") || error.message.includes("404")) {
778
+ console.log(chalk6.dim("\nThe document may not exist or you may not have access."));
779
+ console.log(chalk6.dim("Check the URI and ensure your storage providers are configured correctly."));
780
+ }
781
+ process.exit(1);
782
+ }
783
+ });
784
+ return cmd;
785
+ }
786
+
787
+ // src/commands/cache/index.ts
788
+ init_esm_shims();
789
+
790
+ // src/commands/cache/list.ts
791
+ init_esm_shims();
792
+ function formatSize(bytes) {
793
+ if (bytes < 1024) return `${bytes} B`;
794
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
795
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
796
+ }
797
+ function cacheListCommand() {
798
+ const cmd = new Command("list");
799
+ cmd.description("List cache information").option("--json", "Output as JSON").action(async (options) => {
800
+ try {
801
+ const client = await getClient();
802
+ const stats = await client.getCacheStats();
803
+ if (stats.entryCount === 0) {
804
+ if (options.json) {
805
+ console.log(JSON.stringify({ entries: 0, message: "Cache is empty" }));
806
+ } else {
807
+ console.log(chalk6.yellow("Cache is empty."));
808
+ console.log(chalk6.dim("Fetch some documents to populate the cache."));
809
+ }
810
+ return;
811
+ }
812
+ if (options.json) {
813
+ console.log(JSON.stringify({
814
+ entryCount: stats.entryCount,
815
+ totalSize: stats.totalSize,
816
+ freshCount: stats.freshCount,
817
+ staleCount: stats.staleCount,
818
+ expiredCount: stats.expiredCount
819
+ }, null, 2));
820
+ return;
821
+ }
822
+ console.log(chalk6.bold("Cache Overview\n"));
823
+ console.log(chalk6.bold("Entries:"));
824
+ console.log(` Total: ${chalk6.cyan(stats.entryCount.toString())} entries`);
825
+ console.log(` Fresh: ${chalk6.green(stats.freshCount.toString())} entries`);
826
+ console.log(` Stale: ${stats.staleCount > 0 ? chalk6.yellow(stats.staleCount.toString()) : chalk6.dim("0")} entries`);
827
+ console.log(` Expired: ${stats.expiredCount > 0 ? chalk6.red(stats.expiredCount.toString()) : chalk6.dim("0")} entries`);
828
+ console.log("");
829
+ console.log(chalk6.bold("Storage:"));
830
+ console.log(` Total size: ${chalk6.cyan(formatSize(stats.totalSize))}`);
831
+ console.log("");
832
+ const healthPercent = stats.entryCount > 0 ? stats.freshCount / stats.entryCount * 100 : 100;
833
+ const healthColor = healthPercent > 80 ? chalk6.green : healthPercent > 50 ? chalk6.yellow : chalk6.red;
834
+ console.log(`Cache health: ${healthColor(`${healthPercent.toFixed(0)}% fresh`)}`);
835
+ console.log("");
836
+ console.log(chalk6.dim("Note: Individual cache entries are managed by the SDK."));
837
+ console.log(chalk6.dim('Use "fractary codex cache stats" for detailed statistics.'));
838
+ console.log(chalk6.dim('Use "fractary codex cache clear" to clear cache entries.'));
839
+ } catch (error) {
840
+ console.error(chalk6.red("Error:"), error.message);
841
+ process.exit(1);
842
+ }
843
+ });
844
+ return cmd;
845
+ }
846
+
847
+ // src/commands/cache/clear.ts
848
+ init_esm_shims();
849
+ function cacheClearCommand() {
850
+ const cmd = new Command("clear");
851
+ cmd.description("Clear cache entries").option("--all", "Clear entire cache").option("--pattern <glob>", 'Clear entries matching URI pattern (e.g., "codex://fractary/*")').option("--dry-run", "Show what would be cleared without actually clearing").action(async (options) => {
852
+ try {
853
+ const client = await getClient();
854
+ const statsBefore = await client.getCacheStats();
855
+ if (statsBefore.entryCount === 0) {
856
+ console.log(chalk6.yellow("Cache is already empty. Nothing to clear."));
857
+ return;
858
+ }
859
+ let pattern;
860
+ if (options.all) {
861
+ pattern = void 0;
862
+ } else if (options.pattern) {
863
+ pattern = options.pattern;
864
+ } else {
865
+ console.log(chalk6.yellow("Please specify what to clear:"));
866
+ console.log(chalk6.dim(" --all Clear entire cache"));
867
+ console.log(chalk6.dim(' --pattern Clear entries matching pattern (e.g., "codex://fractary/*")'));
868
+ console.log("");
869
+ console.log(chalk6.dim("Examples:"));
870
+ console.log(chalk6.dim(" fractary codex cache clear --all"));
871
+ console.log(chalk6.dim(' fractary codex cache clear --pattern "codex://fractary/cli/*"'));
872
+ return;
873
+ }
874
+ if (options.dryRun) {
875
+ console.log(chalk6.blue("Dry run - would clear:\n"));
876
+ if (pattern) {
877
+ console.log(chalk6.dim(` Pattern: ${pattern}`));
878
+ console.log(chalk6.dim(` This would invalidate matching cache entries`));
879
+ } else {
880
+ console.log(chalk6.dim(` All cache entries (${statsBefore.entryCount} entries)`));
881
+ }
882
+ console.log(chalk6.dim(`
883
+ Total size: ${formatSize2(statsBefore.totalSize)}`));
884
+ return;
885
+ }
886
+ if (pattern) {
887
+ console.log(chalk6.blue(`Clearing cache entries matching pattern: ${pattern}
888
+ `));
889
+ await client.invalidateCache(pattern);
890
+ } else {
891
+ console.log(chalk6.blue(`Clearing entire cache (${statsBefore.entryCount} entries)...
892
+ `));
893
+ await client.invalidateCache();
894
+ }
895
+ const statsAfter = await client.getCacheStats();
896
+ const entriesCleared = statsBefore.entryCount - statsAfter.entryCount;
897
+ const sizeFreed = statsBefore.totalSize - statsAfter.totalSize;
898
+ console.log(chalk6.green(`\u2713 Cleared ${entriesCleared} entries (${formatSize2(sizeFreed)} freed)`));
899
+ if (statsAfter.entryCount > 0) {
900
+ console.log(chalk6.dim(` Remaining: ${statsAfter.entryCount} entries (${formatSize2(statsAfter.totalSize)})`));
901
+ }
902
+ } catch (error) {
903
+ console.error(chalk6.red("Error:"), error.message);
904
+ process.exit(1);
905
+ }
906
+ });
907
+ return cmd;
908
+ }
909
+ function formatSize2(bytes) {
910
+ if (bytes < 1024) return `${bytes} B`;
911
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
912
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
913
+ }
914
+
915
+ // src/commands/cache/stats.ts
916
+ init_esm_shims();
917
+ function formatSize3(bytes) {
918
+ if (bytes < 1024) return `${bytes} B`;
919
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
920
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
921
+ }
922
+ function cacheStatsCommand() {
923
+ const cmd = new Command("stats");
924
+ cmd.description("Display cache statistics").option("--json", "Output as JSON").action(async (options) => {
925
+ try {
926
+ const client = await getClient();
927
+ const stats = await client.getCacheStats();
928
+ if (options.json) {
929
+ console.log(JSON.stringify(stats, null, 2));
930
+ return;
931
+ }
932
+ console.log(chalk6.bold("Cache Statistics\n"));
933
+ console.log(chalk6.bold("Overview"));
934
+ console.log(` Total entries: ${chalk6.cyan(stats.entryCount.toString())}`);
935
+ console.log(` Total size: ${chalk6.cyan(formatSize3(stats.totalSize))}`);
936
+ console.log(` Fresh entries: ${chalk6.green(stats.freshCount.toString())}`);
937
+ console.log(` Stale entries: ${stats.staleCount > 0 ? chalk6.yellow(stats.staleCount.toString()) : chalk6.dim("0")}`);
938
+ console.log(` Expired entries: ${stats.expiredCount > 0 ? chalk6.red(stats.expiredCount.toString()) : chalk6.dim("0")}`);
939
+ console.log("");
940
+ const healthPercent = stats.entryCount > 0 ? stats.freshCount / stats.entryCount * 100 : 100;
941
+ const healthColor = healthPercent > 80 ? chalk6.green : healthPercent > 50 ? chalk6.yellow : chalk6.red;
942
+ console.log(`Cache health: ${healthColor(`${healthPercent.toFixed(0)}% fresh`)}`);
943
+ if (stats.expiredCount > 0) {
944
+ console.log(chalk6.dim('\nRun "fractary codex cache clear --pattern <pattern>" to clean up entries.'));
945
+ }
946
+ if (stats.entryCount === 0) {
947
+ console.log(chalk6.dim("\nNo cached entries. Fetch some documents to populate the cache."));
948
+ }
949
+ } catch (error) {
950
+ console.error(chalk6.red("Error:"), error.message);
951
+ process.exit(1);
952
+ }
953
+ });
954
+ return cmd;
955
+ }
956
+
957
+ // src/commands/cache/index.ts
958
+ function cacheCommand() {
959
+ const cmd = new Command("cache");
960
+ cmd.description("Manage the codex document cache");
961
+ cmd.addCommand(cacheListCommand());
962
+ cmd.addCommand(cacheClearCommand());
963
+ cmd.addCommand(cacheStatsCommand());
964
+ return cmd;
965
+ }
966
+
967
+ // src/commands/sync/index.ts
968
+ init_esm_shims();
969
+
970
+ // src/commands/sync/project.ts
971
+ init_esm_shims();
972
+ init_migrate_config();
973
+ function getEnvironmentBranch(config, env) {
974
+ const envMap = config.sync?.environments || {
975
+ dev: "develop",
976
+ test: "test",
977
+ staging: "staging",
978
+ prod: "main"
979
+ };
980
+ return envMap[env] || env;
981
+ }
982
+ function formatBytes(bytes) {
983
+ if (bytes < 1024) return `${bytes} B`;
984
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
985
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
986
+ }
987
+ function formatDuration(ms) {
988
+ if (ms < 1e3) return `${ms}ms`;
989
+ return `${(ms / 1e3).toFixed(1)}s`;
990
+ }
991
+ function syncProjectCommand() {
992
+ const cmd = new Command("project");
993
+ cmd.description("Sync single project with codex repository").argument("[name]", "Project name (auto-detected if not provided)").option("--env <env>", "Target environment (dev/test/staging/prod)", "prod").option("--dry-run", "Show what would sync without executing").option("--direction <dir>", "Sync direction (to-codex/from-codex/bidirectional)", "bidirectional").option("--include <pattern>", "Include files matching pattern (can be used multiple times)", (val, prev) => prev.concat([val]), []).option("--exclude <pattern>", "Exclude files matching pattern (can be used multiple times)", (val, prev) => prev.concat([val]), []).option("--force", "Force sync without checking timestamps").option("--json", "Output as JSON").action(async (name, options) => {
994
+ try {
995
+ const configPath = path3.join(process.cwd(), ".fractary", "codex.yaml");
996
+ let config;
997
+ try {
998
+ config = await readYamlConfig(configPath);
999
+ } catch (error) {
1000
+ console.error(chalk6.red("Error:"), "Codex not initialized.");
1001
+ console.log(chalk6.dim('Run "fractary codex init" first.'));
1002
+ process.exit(1);
1003
+ }
1004
+ const { createSyncManager, createLocalStorage, detectCurrentProject } = await import('@fractary/codex');
1005
+ let projectName = name;
1006
+ if (!projectName) {
1007
+ const detected = detectCurrentProject();
1008
+ projectName = detected.project || null;
1009
+ }
1010
+ if (!projectName) {
1011
+ console.error(chalk6.red("Error:"), "Could not determine project name.");
1012
+ console.log(chalk6.dim("Provide project name as argument or run from a git repository."));
1013
+ process.exit(1);
1014
+ }
1015
+ const validDirections = ["to-codex", "from-codex", "bidirectional"];
1016
+ if (!validDirections.includes(options.direction)) {
1017
+ console.error(chalk6.red("Error:"), `Invalid direction: ${options.direction}`);
1018
+ console.log(chalk6.dim("Valid options: to-codex, from-codex, bidirectional"));
1019
+ process.exit(1);
1020
+ }
1021
+ const direction = options.direction;
1022
+ const targetBranch = getEnvironmentBranch(config, options.env);
1023
+ const localStorage = createLocalStorage({
1024
+ baseDir: process.cwd()
1025
+ });
1026
+ const syncManager = createSyncManager({
1027
+ localStorage,
1028
+ config: config.sync,
1029
+ manifestPath: path3.join(process.cwd(), ".fractary", ".codex-sync-manifest.json")
1030
+ });
1031
+ const defaultPatterns = config.sync?.include || [
1032
+ "docs/**/*.md",
1033
+ "specs/**/*.md",
1034
+ ".fractary/standards/**",
1035
+ ".fractary/templates/**"
1036
+ ];
1037
+ const includePatterns = options.include.length > 0 ? options.include : defaultPatterns;
1038
+ const excludePatterns = [
1039
+ ...config.sync?.exclude || [],
1040
+ ...options.exclude
1041
+ ];
1042
+ const sourceDir = process.cwd();
1043
+ const allFiles = await syncManager.listLocalFiles(sourceDir);
1044
+ const targetFiles = allFiles.filter((file) => {
1045
+ for (const pattern of excludePatterns) {
1046
+ const regex = new RegExp("^" + pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*") + "$");
1047
+ if (regex.test(file.path)) {
1048
+ return false;
1049
+ }
1050
+ }
1051
+ for (const pattern of includePatterns) {
1052
+ const regex = new RegExp("^" + pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*") + "$");
1053
+ if (regex.test(file.path)) {
1054
+ return true;
1055
+ }
1056
+ }
1057
+ return false;
1058
+ });
1059
+ const syncOptions = {
1060
+ direction,
1061
+ dryRun: options.dryRun,
1062
+ force: options.force,
1063
+ include: includePatterns,
1064
+ exclude: excludePatterns
1065
+ };
1066
+ const plan = await syncManager.createPlan(
1067
+ config.organization,
1068
+ projectName,
1069
+ sourceDir,
1070
+ targetFiles,
1071
+ syncOptions
1072
+ );
1073
+ if (plan.totalFiles === 0) {
1074
+ if (options.json) {
1075
+ console.log(JSON.stringify({
1076
+ project: projectName,
1077
+ organization: config.organization,
1078
+ files: [],
1079
+ synced: 0
1080
+ }, null, 2));
1081
+ } else {
1082
+ console.log(chalk6.yellow("No files to sync."));
1083
+ }
1084
+ return;
1085
+ }
1086
+ if (options.json) {
1087
+ const output = {
1088
+ project: projectName,
1089
+ organization: config.organization,
1090
+ environment: options.env,
1091
+ branch: targetBranch,
1092
+ direction,
1093
+ dryRun: options.dryRun || false,
1094
+ plan: {
1095
+ totalFiles: plan.totalFiles,
1096
+ totalBytes: plan.totalBytes,
1097
+ estimatedTime: plan.estimatedTime,
1098
+ conflicts: plan.conflicts.length,
1099
+ skipped: plan.skipped.length
1100
+ },
1101
+ files: plan.files.map((f) => ({
1102
+ path: f.path,
1103
+ operation: f.operation,
1104
+ size: f.size
1105
+ }))
1106
+ };
1107
+ if (options.dryRun) {
1108
+ console.log(JSON.stringify(output, null, 2));
1109
+ return;
1110
+ }
1111
+ const result2 = await syncManager.executePlan(plan, syncOptions);
1112
+ console.log(JSON.stringify({
1113
+ ...output,
1114
+ result: {
1115
+ success: result2.success,
1116
+ synced: result2.synced,
1117
+ failed: result2.failed,
1118
+ skipped: result2.skipped,
1119
+ duration: result2.duration,
1120
+ errors: result2.errors
1121
+ }
1122
+ }, null, 2));
1123
+ return;
1124
+ }
1125
+ console.log(chalk6.bold("Sync Plan\n"));
1126
+ console.log(` Project: ${chalk6.cyan(projectName)}`);
1127
+ console.log(` Organization: ${chalk6.cyan(config.organization)}`);
1128
+ console.log(` Environment: ${chalk6.cyan(options.env)} (${targetBranch})`);
1129
+ console.log(` Direction: ${chalk6.cyan(direction)}`);
1130
+ console.log(` Files: ${chalk6.cyan(plan.totalFiles.toString())}`);
1131
+ console.log(` Total size: ${chalk6.cyan(formatBytes(plan.totalBytes))}`);
1132
+ if (plan.estimatedTime) {
1133
+ console.log(` Est. time: ${chalk6.dim(formatDuration(plan.estimatedTime))}`);
1134
+ }
1135
+ console.log("");
1136
+ if (plan.conflicts.length > 0) {
1137
+ console.log(chalk6.yellow(`\u26A0 ${plan.conflicts.length} conflicts detected:`));
1138
+ for (const conflict of plan.conflicts.slice(0, 5)) {
1139
+ console.log(chalk6.yellow(` \u2022 ${conflict.path}`));
1140
+ }
1141
+ if (plan.conflicts.length > 5) {
1142
+ console.log(chalk6.dim(` ... and ${plan.conflicts.length - 5} more`));
1143
+ }
1144
+ console.log("");
1145
+ }
1146
+ if (plan.skipped.length > 0) {
1147
+ console.log(chalk6.dim(`${plan.skipped.length} files skipped (no changes)`));
1148
+ console.log("");
1149
+ }
1150
+ if (options.dryRun) {
1151
+ console.log(chalk6.blue("Dry run - would sync:\n"));
1152
+ const filesToShow = plan.files.slice(0, 10);
1153
+ for (const file of filesToShow) {
1154
+ const arrow = direction === "to-codex" ? "\u2192" : direction === "from-codex" ? "\u2190" : "\u2194";
1155
+ const opColor = file.operation === "create" ? chalk6.green : file.operation === "update" ? chalk6.yellow : chalk6.dim;
1156
+ console.log(
1157
+ chalk6.dim(` ${arrow}`),
1158
+ opColor(file.operation.padEnd(7)),
1159
+ file.path,
1160
+ chalk6.dim(`(${formatBytes(file.size || 0)})`)
1161
+ );
1162
+ }
1163
+ if (plan.files.length > 10) {
1164
+ console.log(chalk6.dim(` ... and ${plan.files.length - 10} more files`));
1165
+ }
1166
+ console.log(chalk6.dim(`
1167
+ Total: ${plan.totalFiles} files (${formatBytes(plan.totalBytes)})`));
1168
+ console.log(chalk6.dim("Run without --dry-run to execute sync."));
1169
+ return;
1170
+ }
1171
+ console.log(chalk6.blue("Syncing...\n"));
1172
+ const startTime = Date.now();
1173
+ const result = await syncManager.executePlan(plan, syncOptions);
1174
+ const duration = Date.now() - startTime;
1175
+ console.log("");
1176
+ if (result.success) {
1177
+ console.log(chalk6.green(`\u2713 Sync completed successfully`));
1178
+ console.log(chalk6.dim(` Synced: ${result.synced} files`));
1179
+ if (result.skipped > 0) {
1180
+ console.log(chalk6.dim(` Skipped: ${result.skipped} files`));
1181
+ }
1182
+ console.log(chalk6.dim(` Duration: ${formatDuration(duration)}`));
1183
+ } else {
1184
+ console.log(chalk6.yellow(`\u26A0 Sync completed with errors`));
1185
+ console.log(chalk6.green(` Synced: ${result.synced} files`));
1186
+ console.log(chalk6.red(` Failed: ${result.failed} files`));
1187
+ if (result.skipped > 0) {
1188
+ console.log(chalk6.dim(` Skipped: ${result.skipped} files`));
1189
+ }
1190
+ if (result.errors.length > 0) {
1191
+ console.log("");
1192
+ console.log(chalk6.red("Errors:"));
1193
+ for (const error of result.errors.slice(0, 5)) {
1194
+ console.log(chalk6.red(` \u2022 ${error.path}: ${error.error}`));
1195
+ }
1196
+ if (result.errors.length > 5) {
1197
+ console.log(chalk6.dim(` ... and ${result.errors.length - 5} more errors`));
1198
+ }
1199
+ }
1200
+ }
1201
+ } catch (error) {
1202
+ console.error(chalk6.red("Error:"), error.message);
1203
+ process.exit(1);
1204
+ }
1205
+ });
1206
+ return cmd;
1207
+ }
1208
+
1209
+ // src/commands/sync/org.ts
1210
+ init_esm_shims();
1211
+ init_migrate_config();
1212
+ function getEnvironmentBranch2(config, env) {
1213
+ const envMap = config.sync?.environments || {
1214
+ dev: "develop",
1215
+ test: "test",
1216
+ staging: "staging",
1217
+ prod: "main"
1218
+ };
1219
+ return envMap[env] || env;
1220
+ }
1221
+ async function discoverRepos(org) {
1222
+ try {
1223
+ const output = execSync(
1224
+ `gh repo list ${org} --json name,url,defaultBranchRef --limit 100`,
1225
+ { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 }
1226
+ );
1227
+ const repos = JSON.parse(output);
1228
+ return repos.map((repo) => ({
1229
+ name: repo.name,
1230
+ url: repo.url,
1231
+ defaultBranch: repo.defaultBranchRef?.name || "main"
1232
+ }));
1233
+ } catch (error) {
1234
+ throw new Error(`Failed to discover repos: ${error.message}. Ensure GitHub CLI is installed and authenticated.`);
1235
+ }
1236
+ }
1237
+ function shouldExclude(repoName, excludePatterns) {
1238
+ for (const pattern of excludePatterns) {
1239
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
1240
+ if (regex.test(repoName)) {
1241
+ return true;
1242
+ }
1243
+ }
1244
+ return false;
1245
+ }
1246
+ async function syncRepository(repo, config, direction, syncOptions) {
1247
+ const startTime = Date.now();
1248
+ try {
1249
+ const { createSyncManager, createLocalStorage } = await import('@fractary/codex');
1250
+ const repoDir = path3.join(process.cwd(), "..", repo.name);
1251
+ const localStorage = createLocalStorage({
1252
+ baseDir: repoDir
1253
+ });
1254
+ const syncManager = createSyncManager({
1255
+ localStorage,
1256
+ config: config.sync,
1257
+ manifestPath: path3.join(repoDir, ".fractary", ".codex-sync-manifest.json")
1258
+ });
1259
+ const includePatterns = config.sync?.include || [
1260
+ "docs/**/*.md",
1261
+ "specs/**/*.md",
1262
+ ".fractary/standards/**",
1263
+ ".fractary/templates/**"
1264
+ ];
1265
+ const allFiles = await syncManager.listLocalFiles(repoDir);
1266
+ const targetFiles = allFiles.filter((file) => {
1267
+ for (const pattern of includePatterns) {
1268
+ const regex = new RegExp("^" + pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*") + "$");
1269
+ if (regex.test(file.path)) {
1270
+ return true;
1271
+ }
1272
+ }
1273
+ return false;
1274
+ });
1275
+ const plan = await syncManager.createPlan(
1276
+ config.organization,
1277
+ repo.name,
1278
+ repoDir,
1279
+ targetFiles,
1280
+ syncOptions
1281
+ );
1282
+ if (plan.totalFiles === 0) {
1283
+ return {
1284
+ repo: repo.name,
1285
+ status: "skipped",
1286
+ filesSynced: 0,
1287
+ duration: Date.now() - startTime
1288
+ };
1289
+ }
1290
+ const result = await syncManager.executePlan(plan, syncOptions);
1291
+ return {
1292
+ repo: repo.name,
1293
+ status: result.success ? "success" : "error",
1294
+ filesSynced: result.synced,
1295
+ duration: Date.now() - startTime,
1296
+ error: result.success ? void 0 : `${result.failed} files failed`
1297
+ };
1298
+ } catch (error) {
1299
+ return {
1300
+ repo: repo.name,
1301
+ status: "error",
1302
+ duration: Date.now() - startTime,
1303
+ error: error.message
1304
+ };
1305
+ }
1306
+ }
1307
+ function syncOrgCommand() {
1308
+ const cmd = new Command("org");
1309
+ cmd.description("Sync all projects in organization with codex repository").option("--env <env>", "Target environment (dev/test/staging/prod)", "prod").option("--dry-run", "Show what would sync without executing").option("--exclude <pattern>", "Exclude repos matching pattern (can be used multiple times)", (val, prev) => prev.concat([val]), []).option("--parallel <n>", "Number of parallel syncs", parseInt, 3).option("--direction <dir>", "Sync direction (to-codex/from-codex/bidirectional)", "bidirectional").option("--json", "Output as JSON").action(async (options) => {
1310
+ try {
1311
+ const configPath = path3.join(process.cwd(), ".fractary", "codex.yaml");
1312
+ let config;
1313
+ try {
1314
+ config = await readYamlConfig(configPath);
1315
+ } catch (error) {
1316
+ console.error(chalk6.red("Error:"), "Codex not initialized.");
1317
+ console.log(chalk6.dim('Run "fractary codex init" first.'));
1318
+ process.exit(1);
1319
+ }
1320
+ const validDirections = ["to-codex", "from-codex", "bidirectional"];
1321
+ if (!validDirections.includes(options.direction)) {
1322
+ console.error(chalk6.red("Error:"), `Invalid direction: ${options.direction}`);
1323
+ console.log(chalk6.dim("Valid options: to-codex, from-codex, bidirectional"));
1324
+ process.exit(1);
1325
+ }
1326
+ const direction = options.direction;
1327
+ const org = config.organization;
1328
+ const targetBranch = getEnvironmentBranch2(config, options.env);
1329
+ const excludePatterns = [
1330
+ ...config.sync?.exclude || [],
1331
+ ...options.exclude
1332
+ ];
1333
+ if (!options.json) {
1334
+ console.log(chalk6.bold("Organization Sync\n"));
1335
+ console.log(` Organization: ${chalk6.cyan(org)}`);
1336
+ console.log(` Environment: ${chalk6.cyan(options.env)} (${targetBranch})`);
1337
+ console.log(` Direction: ${chalk6.cyan(direction)}`);
1338
+ console.log(` Parallelism: ${chalk6.cyan(options.parallel.toString())}`);
1339
+ if (excludePatterns.length > 0) {
1340
+ console.log(` Excluding: ${chalk6.dim(excludePatterns.join(", "))}`);
1341
+ }
1342
+ console.log("");
1343
+ console.log(chalk6.dim("Discovering repositories..."));
1344
+ }
1345
+ let repos;
1346
+ try {
1347
+ repos = await discoverRepos(org);
1348
+ } catch (error) {
1349
+ if (options.json) {
1350
+ console.log(JSON.stringify({ error: error.message }));
1351
+ } else {
1352
+ console.error(chalk6.red("Error:"), error.message);
1353
+ }
1354
+ process.exit(1);
1355
+ }
1356
+ const eligibleRepos = repos.filter((repo) => !shouldExclude(repo.name, excludePatterns));
1357
+ const excludedRepos = repos.filter((repo) => shouldExclude(repo.name, excludePatterns));
1358
+ if (!options.json) {
1359
+ console.log(chalk6.dim(`Found ${repos.length} repositories (${excludedRepos.length} excluded)
1360
+ `));
1361
+ }
1362
+ if (options.dryRun) {
1363
+ if (options.json) {
1364
+ console.log(JSON.stringify({
1365
+ organization: org,
1366
+ environment: options.env,
1367
+ branch: targetBranch,
1368
+ direction,
1369
+ dryRun: true,
1370
+ repos: {
1371
+ total: repos.length,
1372
+ eligible: eligibleRepos.map((r) => r.name),
1373
+ excluded: excludedRepos.map((r) => r.name)
1374
+ }
1375
+ }, null, 2));
1376
+ } else {
1377
+ console.log(chalk6.blue("Dry run - would sync:\n"));
1378
+ for (const repo of eligibleRepos) {
1379
+ console.log(chalk6.green(" \u2713"), repo.name);
1380
+ }
1381
+ if (excludedRepos.length > 0) {
1382
+ console.log("");
1383
+ console.log(chalk6.dim("Excluded:"));
1384
+ for (const repo of excludedRepos) {
1385
+ console.log(chalk6.dim(` - ${repo.name}`));
1386
+ }
1387
+ }
1388
+ console.log(chalk6.dim(`
1389
+ Total: ${eligibleRepos.length} repos would be synced`));
1390
+ console.log(chalk6.dim("Run without --dry-run to execute sync."));
1391
+ }
1392
+ return;
1393
+ }
1394
+ if (!options.json) {
1395
+ console.log(chalk6.blue("Syncing repositories...\n"));
1396
+ }
1397
+ const results = [];
1398
+ const syncOptions = {
1399
+ direction,
1400
+ dryRun: false,
1401
+ force: false
1402
+ };
1403
+ for (let i = 0; i < eligibleRepos.length; i += options.parallel) {
1404
+ const batch = eligibleRepos.slice(i, i + options.parallel);
1405
+ const batchResults = await Promise.all(
1406
+ batch.map((repo) => syncRepository(repo, config, direction, syncOptions))
1407
+ );
1408
+ for (const result of batchResults) {
1409
+ results.push(result);
1410
+ if (!options.json) {
1411
+ if (result.status === "success") {
1412
+ console.log(
1413
+ chalk6.green(" \u2713"),
1414
+ result.repo,
1415
+ chalk6.dim(`(${result.filesSynced} files in ${result.duration}ms)`)
1416
+ );
1417
+ } else if (result.status === "skipped") {
1418
+ console.log(
1419
+ chalk6.dim(" -"),
1420
+ result.repo,
1421
+ chalk6.dim("(no files to sync)")
1422
+ );
1423
+ } else if (result.status === "error") {
1424
+ console.log(
1425
+ chalk6.red(" \u2717"),
1426
+ result.repo,
1427
+ chalk6.red(`(${result.error})`)
1428
+ );
1429
+ }
1430
+ }
1431
+ }
1432
+ }
1433
+ const successful = results.filter((r) => r.status === "success");
1434
+ const skipped = results.filter((r) => r.status === "skipped");
1435
+ const failed = results.filter((r) => r.status === "error");
1436
+ const totalFiles = successful.reduce((sum, r) => sum + (r.filesSynced || 0), 0);
1437
+ if (options.json) {
1438
+ console.log(JSON.stringify({
1439
+ organization: org,
1440
+ environment: options.env,
1441
+ branch: targetBranch,
1442
+ direction,
1443
+ results: {
1444
+ total: results.length,
1445
+ successful: successful.length,
1446
+ skipped: skipped.length,
1447
+ failed: failed.length,
1448
+ filesSynced: totalFiles,
1449
+ details: results
1450
+ }
1451
+ }, null, 2));
1452
+ } else {
1453
+ console.log("");
1454
+ console.log(chalk6.green(`\u2713 Synced ${successful.length}/${results.length} repos (${totalFiles} files)`));
1455
+ if (skipped.length > 0) {
1456
+ console.log(chalk6.dim(` Skipped ${skipped.length} repos (no changes)`));
1457
+ }
1458
+ if (failed.length > 0) {
1459
+ console.log(chalk6.red(`\u2717 ${failed.length} repos failed`));
1460
+ }
1461
+ }
1462
+ } catch (error) {
1463
+ if (options.json) {
1464
+ console.log(JSON.stringify({ error: error.message }));
1465
+ } else {
1466
+ console.error(chalk6.red("Error:"), error.message);
1467
+ }
1468
+ process.exit(1);
1469
+ }
1470
+ });
1471
+ return cmd;
1472
+ }
1473
+
1474
+ // src/commands/sync/index.ts
1475
+ function syncCommand() {
1476
+ const cmd = new Command("sync");
1477
+ cmd.description("Synchronize with codex repository");
1478
+ cmd.addCommand(syncProjectCommand());
1479
+ cmd.addCommand(syncOrgCommand());
1480
+ return cmd;
1481
+ }
1482
+
1483
+ // src/commands/types/index.ts
1484
+ init_esm_shims();
1485
+
1486
+ // src/commands/types/list.ts
1487
+ init_esm_shims();
1488
+ function formatTtl(seconds) {
1489
+ if (seconds < 60) return `${seconds}s`;
1490
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
1491
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`;
1492
+ return `${Math.floor(seconds / 86400)}d`;
1493
+ }
1494
+ function typesListCommand() {
1495
+ const cmd = new Command("list");
1496
+ cmd.description("List all artifact types").option("--json", "Output as JSON").option("--custom-only", "Show only custom types").option("--builtin-only", "Show only built-in types").action(async (options) => {
1497
+ try {
1498
+ const client = await getClient();
1499
+ const registry = client.getTypeRegistry();
1500
+ const allTypes = registry.list();
1501
+ let types = allTypes;
1502
+ if (options.customOnly) {
1503
+ types = allTypes.filter((t) => !registry.isBuiltIn(t.name));
1504
+ } else if (options.builtinOnly) {
1505
+ types = allTypes.filter((t) => registry.isBuiltIn(t.name));
1506
+ }
1507
+ if (options.json) {
1508
+ const builtinCount = types.filter((t) => registry.isBuiltIn(t.name)).length;
1509
+ const customCount = types.length - builtinCount;
1510
+ console.log(JSON.stringify({
1511
+ count: types.length,
1512
+ builtinCount,
1513
+ customCount,
1514
+ types: types.map((t) => ({
1515
+ name: t.name,
1516
+ description: t.description,
1517
+ patterns: t.patterns,
1518
+ defaultTtl: t.defaultTtl,
1519
+ ttl: formatTtl(t.defaultTtl),
1520
+ builtin: registry.isBuiltIn(t.name),
1521
+ archiveAfterDays: t.archiveAfterDays,
1522
+ archiveStorage: t.archiveStorage
1523
+ }))
1524
+ }, null, 2));
1525
+ return;
1526
+ }
1527
+ if (types.length === 0) {
1528
+ console.log(chalk6.yellow("No types found."));
1529
+ return;
1530
+ }
1531
+ console.log(chalk6.bold("Artifact Types\n"));
1532
+ const builtinTypes = types.filter((t) => registry.isBuiltIn(t.name));
1533
+ const customTypes = types.filter((t) => !registry.isBuiltIn(t.name));
1534
+ if (builtinTypes.length > 0 && !options.customOnly) {
1535
+ console.log(chalk6.bold("Built-in Types"));
1536
+ console.log(chalk6.dim("\u2500".repeat(70)));
1537
+ for (const type of builtinTypes) {
1538
+ const patternStr = type.patterns[0] || "";
1539
+ console.log(` ${chalk6.cyan(type.name.padEnd(12))} ${patternStr.padEnd(30)} ${chalk6.dim(`TTL: ${formatTtl(type.defaultTtl)}`)}`);
1540
+ console.log(` ${chalk6.dim(" ".repeat(12) + type.description)}`);
1541
+ }
1542
+ console.log("");
1543
+ }
1544
+ if (customTypes.length > 0 && !options.builtinOnly) {
1545
+ console.log(chalk6.bold("Custom Types"));
1546
+ console.log(chalk6.dim("\u2500".repeat(70)));
1547
+ for (const type of customTypes) {
1548
+ const patternStr = type.patterns[0] || "";
1549
+ console.log(` ${chalk6.green(type.name.padEnd(12))} ${patternStr.padEnd(30)} ${chalk6.dim(`TTL: ${formatTtl(type.defaultTtl)}`)}`);
1550
+ console.log(` ${chalk6.dim(" ".repeat(12) + type.description)}`);
1551
+ }
1552
+ console.log("");
1553
+ }
1554
+ console.log(chalk6.dim(`Total: ${types.length} types (${builtinTypes.length} built-in, ${customTypes.length} custom)`));
1555
+ } catch (error) {
1556
+ console.error(chalk6.red("Error:"), error.message);
1557
+ process.exit(1);
1558
+ }
1559
+ });
1560
+ return cmd;
1561
+ }
1562
+
1563
+ // src/commands/types/show.ts
1564
+ init_esm_shims();
1565
+ function formatTtl2(seconds) {
1566
+ if (seconds < 60) return `${seconds}s`;
1567
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
1568
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`;
1569
+ return `${Math.floor(seconds / 86400)}d`;
1570
+ }
1571
+ function typesShowCommand() {
1572
+ const cmd = new Command("show");
1573
+ cmd.description("Show details for a specific type").argument("<name>", "Type name").option("--json", "Output as JSON").action(async (name, options) => {
1574
+ try {
1575
+ const client = await getClient();
1576
+ const registry = client.getTypeRegistry();
1577
+ const type = registry.get(name);
1578
+ if (!type) {
1579
+ console.error(chalk6.red("Error:"), `Type "${name}" not found.`);
1580
+ console.log(chalk6.dim('Run "fractary codex types list" to see available types.'));
1581
+ process.exit(1);
1582
+ }
1583
+ const isBuiltin = registry.isBuiltIn(name);
1584
+ if (options.json) {
1585
+ console.log(JSON.stringify({
1586
+ name: type.name,
1587
+ builtin: isBuiltin,
1588
+ description: type.description,
1589
+ patterns: type.patterns,
1590
+ defaultTtl: type.defaultTtl,
1591
+ ttl: formatTtl2(type.defaultTtl),
1592
+ archiveAfterDays: type.archiveAfterDays,
1593
+ archiveStorage: type.archiveStorage,
1594
+ syncPatterns: type.syncPatterns,
1595
+ excludePatterns: type.excludePatterns
1596
+ }, null, 2));
1597
+ return;
1598
+ }
1599
+ const nameColor = isBuiltin ? chalk6.cyan : chalk6.green;
1600
+ console.log(chalk6.bold(`Type: ${nameColor(name)}
1601
+ `));
1602
+ console.log(` ${chalk6.dim("Source:")} ${isBuiltin ? "Built-in" : "Custom"}`);
1603
+ console.log(` ${chalk6.dim("Description:")} ${type.description}`);
1604
+ console.log(` ${chalk6.dim("TTL:")} ${formatTtl2(type.defaultTtl)} (${type.defaultTtl} seconds)`);
1605
+ console.log("");
1606
+ console.log(chalk6.bold("Patterns"));
1607
+ for (const pattern of type.patterns) {
1608
+ console.log(` ${chalk6.dim("\u2022")} ${pattern}`);
1609
+ }
1610
+ if (type.archiveAfterDays !== null) {
1611
+ console.log("");
1612
+ console.log(chalk6.bold("Archive Settings"));
1613
+ console.log(` ${chalk6.dim("After:")} ${type.archiveAfterDays} days`);
1614
+ console.log(` ${chalk6.dim("Storage:")} ${type.archiveStorage || "not set"}`);
1615
+ }
1616
+ if (type.syncPatterns && type.syncPatterns.length > 0) {
1617
+ console.log("");
1618
+ console.log(chalk6.bold("Sync Patterns"));
1619
+ for (const pattern of type.syncPatterns) {
1620
+ console.log(` ${chalk6.dim("\u2022")} ${pattern}`);
1621
+ }
1622
+ }
1623
+ if (type.excludePatterns && type.excludePatterns.length > 0) {
1624
+ console.log("");
1625
+ console.log(chalk6.bold("Exclude Patterns"));
1626
+ for (const pattern of type.excludePatterns) {
1627
+ console.log(` ${chalk6.dim("\u2022")} ${pattern}`);
1628
+ }
1629
+ }
1630
+ } catch (error) {
1631
+ console.error(chalk6.red("Error:"), error.message);
1632
+ process.exit(1);
1633
+ }
1634
+ });
1635
+ return cmd;
1636
+ }
1637
+
1638
+ // src/commands/types/add.ts
1639
+ init_esm_shims();
1640
+ init_migrate_config();
1641
+ function isValidTypeName(name) {
1642
+ return /^[a-z][a-z0-9-]*$/.test(name);
1643
+ }
1644
+ function parseTtl(ttl) {
1645
+ const match = ttl.match(/^(\d+)([smhd])$/);
1646
+ if (!match) {
1647
+ throw new Error("Invalid TTL format");
1648
+ }
1649
+ const value = parseInt(match[1], 10);
1650
+ const unit = match[2];
1651
+ switch (unit) {
1652
+ case "s":
1653
+ return value;
1654
+ case "m":
1655
+ return value * 60;
1656
+ case "h":
1657
+ return value * 3600;
1658
+ case "d":
1659
+ return value * 86400;
1660
+ default:
1661
+ throw new Error("Unknown TTL unit");
1662
+ }
1663
+ }
1664
+ function formatTtl3(seconds) {
1665
+ if (seconds < 60) return `${seconds}s`;
1666
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
1667
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`;
1668
+ return `${Math.floor(seconds / 86400)}d`;
1669
+ }
1670
+ function typesAddCommand() {
1671
+ const cmd = new Command("add");
1672
+ cmd.description("Add a custom artifact type").argument("<name>", "Type name (lowercase, alphanumeric with hyphens)").requiredOption("--pattern <glob>", "File pattern (glob syntax)").option("--ttl <duration>", 'Cache TTL (e.g., "24h", "7d")', "24h").option("--description <text>", "Type description").option("--json", "Output as JSON").action(async (name, options) => {
1673
+ try {
1674
+ if (!isValidTypeName(name)) {
1675
+ console.error(chalk6.red("Error:"), "Invalid type name.");
1676
+ console.log(chalk6.dim("Type name must be lowercase, start with a letter, and contain only letters, numbers, and hyphens."));
1677
+ process.exit(1);
1678
+ }
1679
+ const client = await getClient();
1680
+ const registry = client.getTypeRegistry();
1681
+ if (registry.isBuiltIn(name)) {
1682
+ console.error(chalk6.red("Error:"), `Cannot override built-in type "${name}".`);
1683
+ const builtinNames = registry.list().filter((t) => registry.isBuiltIn(t.name)).map((t) => t.name);
1684
+ console.log(chalk6.dim("Built-in types: " + builtinNames.join(", ")));
1685
+ process.exit(1);
1686
+ }
1687
+ if (registry.has(name)) {
1688
+ console.error(chalk6.red("Error:"), `Custom type "${name}" already exists.`);
1689
+ console.log(chalk6.dim('Use "fractary codex types remove" first to remove it.'));
1690
+ process.exit(1);
1691
+ }
1692
+ let ttlSeconds;
1693
+ try {
1694
+ ttlSeconds = parseTtl(options.ttl);
1695
+ } catch {
1696
+ console.error(chalk6.red("Error:"), "Invalid TTL format.");
1697
+ console.log(chalk6.dim("Expected format: <number><unit> where unit is s (seconds), m (minutes), h (hours), or d (days)"));
1698
+ console.log(chalk6.dim("Examples: 30m, 24h, 7d"));
1699
+ process.exit(1);
1700
+ }
1701
+ const configPath = path3.join(process.cwd(), ".fractary", "codex.yaml");
1702
+ const config = await readYamlConfig(configPath);
1703
+ if (!config.types) {
1704
+ config.types = { custom: {} };
1705
+ }
1706
+ if (!config.types.custom) {
1707
+ config.types.custom = {};
1708
+ }
1709
+ config.types.custom[name] = {
1710
+ description: options.description || `Custom type: ${name}`,
1711
+ patterns: [options.pattern],
1712
+ defaultTtl: ttlSeconds
1713
+ };
1714
+ await writeYamlConfig(config, configPath);
1715
+ if (options.json) {
1716
+ console.log(JSON.stringify({
1717
+ success: true,
1718
+ type: {
1719
+ name,
1720
+ description: config.types.custom[name].description,
1721
+ patterns: config.types.custom[name].patterns,
1722
+ defaultTtl: ttlSeconds,
1723
+ ttl: formatTtl3(ttlSeconds),
1724
+ builtin: false
1725
+ },
1726
+ message: "Custom type added successfully. Changes will take effect on next CLI invocation."
1727
+ }, null, 2));
1728
+ return;
1729
+ }
1730
+ console.log(chalk6.green("\u2713"), `Added custom type "${chalk6.cyan(name)}"`);
1731
+ console.log("");
1732
+ console.log(` ${chalk6.dim("Pattern:")} ${options.pattern}`);
1733
+ console.log(` ${chalk6.dim("TTL:")} ${formatTtl3(ttlSeconds)} (${ttlSeconds} seconds)`);
1734
+ if (options.description) {
1735
+ console.log(` ${chalk6.dim("Description:")} ${options.description}`);
1736
+ }
1737
+ console.log("");
1738
+ console.log(chalk6.dim("Note: Custom type will be available on next CLI invocation."));
1739
+ } catch (error) {
1740
+ console.error(chalk6.red("Error:"), error.message);
1741
+ if (error.message.includes("Failed to load configuration")) {
1742
+ console.log(chalk6.dim('\nRun "fractary codex init" to create a configuration.'));
1743
+ }
1744
+ process.exit(1);
1745
+ }
1746
+ });
1747
+ return cmd;
1748
+ }
1749
+
1750
+ // src/commands/types/remove.ts
1751
+ init_esm_shims();
1752
+ init_migrate_config();
1753
+ function typesRemoveCommand() {
1754
+ const cmd = new Command("remove");
1755
+ cmd.description("Remove a custom artifact type").argument("<name>", "Type name to remove").option("--json", "Output as JSON").option("--force", "Skip confirmation").action(async (name, options) => {
1756
+ try {
1757
+ const client = await getClient();
1758
+ const registry = client.getTypeRegistry();
1759
+ if (registry.isBuiltIn(name)) {
1760
+ console.error(chalk6.red("Error:"), `Cannot remove built-in type "${name}".`);
1761
+ console.log(chalk6.dim("Built-in types are permanent and cannot be removed."));
1762
+ process.exit(1);
1763
+ }
1764
+ if (!registry.has(name)) {
1765
+ console.error(chalk6.red("Error:"), `Custom type "${name}" not found.`);
1766
+ console.log(chalk6.dim('Run "fractary codex types list --custom-only" to see custom types.'));
1767
+ process.exit(1);
1768
+ }
1769
+ const typeInfo = registry.get(name);
1770
+ const configPath = path3.join(process.cwd(), ".fractary", "codex.yaml");
1771
+ const config = await readYamlConfig(configPath);
1772
+ if (!config.types?.custom?.[name]) {
1773
+ console.error(chalk6.red("Error:"), `Custom type "${name}" not found in configuration.`);
1774
+ process.exit(1);
1775
+ }
1776
+ delete config.types.custom[name];
1777
+ if (Object.keys(config.types.custom).length === 0) {
1778
+ delete config.types.custom;
1779
+ }
1780
+ if (config.types && Object.keys(config.types).length === 0) {
1781
+ delete config.types;
1782
+ }
1783
+ await writeYamlConfig(config, configPath);
1784
+ if (options.json) {
1785
+ console.log(JSON.stringify({
1786
+ success: true,
1787
+ removed: {
1788
+ name: typeInfo.name,
1789
+ description: typeInfo.description,
1790
+ patterns: typeInfo.patterns,
1791
+ defaultTtl: typeInfo.defaultTtl
1792
+ },
1793
+ message: "Custom type removed successfully. Changes will take effect on next CLI invocation."
1794
+ }, null, 2));
1795
+ return;
1796
+ }
1797
+ console.log(chalk6.green("\u2713"), `Removed custom type "${chalk6.cyan(name)}"`);
1798
+ console.log("");
1799
+ console.log(chalk6.dim("Removed configuration:"));
1800
+ console.log(` ${chalk6.dim("Pattern:")} ${typeInfo.patterns.join(", ")}`);
1801
+ console.log(` ${chalk6.dim("Description:")} ${typeInfo.description}`);
1802
+ console.log("");
1803
+ console.log(chalk6.dim("Note: Custom type will be removed on next CLI invocation."));
1804
+ } catch (error) {
1805
+ console.error(chalk6.red("Error:"), error.message);
1806
+ if (error.message.includes("Failed to load configuration")) {
1807
+ console.log(chalk6.dim('\nRun "fractary codex init" to create a configuration.'));
1808
+ }
1809
+ process.exit(1);
1810
+ }
1811
+ });
1812
+ return cmd;
1813
+ }
1814
+
1815
+ // src/commands/types/index.ts
1816
+ function typesCommand() {
1817
+ const cmd = new Command("types");
1818
+ cmd.description("Manage artifact type registry");
1819
+ cmd.addCommand(typesListCommand());
1820
+ cmd.addCommand(typesShowCommand());
1821
+ cmd.addCommand(typesAddCommand());
1822
+ cmd.addCommand(typesRemoveCommand());
1823
+ return cmd;
1824
+ }
1825
+
1826
+ // src/commands/health.ts
1827
+ init_esm_shims();
1828
+ init_migrate_config();
1829
+ async function fileExists2(filePath) {
1830
+ try {
1831
+ await fs.access(filePath);
1832
+ return true;
1833
+ } catch {
1834
+ return false;
1835
+ }
1836
+ }
1837
+ async function checkConfiguration() {
1838
+ const configPath = path3.join(process.cwd(), ".fractary", "codex.yaml");
1839
+ const legacyConfigPath = path3.join(process.cwd(), ".fractary", "plugins", "codex", "config.json");
1840
+ try {
1841
+ if (!await fileExists2(configPath)) {
1842
+ if (await fileExists2(legacyConfigPath)) {
1843
+ return {
1844
+ name: "Configuration",
1845
+ status: "warn",
1846
+ message: "Legacy JSON configuration detected",
1847
+ details: 'Run "fractary codex migrate" to upgrade to YAML format'
1848
+ };
1849
+ }
1850
+ return {
1851
+ name: "Configuration",
1852
+ status: "fail",
1853
+ message: "No configuration found",
1854
+ details: 'Run "fractary codex init" to create configuration'
1855
+ };
1856
+ }
1857
+ const config = await readYamlConfig(configPath);
1858
+ if (!config.organization) {
1859
+ return {
1860
+ name: "Configuration",
1861
+ status: "warn",
1862
+ message: "No organization configured",
1863
+ details: "Organization slug is required"
1864
+ };
1865
+ }
1866
+ const providerCount = config.storage?.length || 0;
1867
+ if (providerCount === 0) {
1868
+ return {
1869
+ name: "Configuration",
1870
+ status: "warn",
1871
+ message: "No storage providers configured",
1872
+ details: "At least one storage provider is recommended"
1873
+ };
1874
+ }
1875
+ return {
1876
+ name: "Configuration",
1877
+ status: "pass",
1878
+ message: "Valid YAML configuration",
1879
+ details: `Organization: ${config.organization}, ${providerCount} storage provider(s)`
1880
+ };
1881
+ } catch (error) {
1882
+ return {
1883
+ name: "Configuration",
1884
+ status: "fail",
1885
+ message: "Invalid configuration",
1886
+ details: error.message
1887
+ };
1888
+ }
1889
+ }
1890
+ async function checkSDKClient() {
1891
+ try {
1892
+ const client = await getClient();
1893
+ const organization = client.getOrganization();
1894
+ return {
1895
+ name: "SDK Client",
1896
+ status: "pass",
1897
+ message: "CodexClient initialized successfully",
1898
+ details: `Organization: ${organization}`
1899
+ };
1900
+ } catch (error) {
1901
+ return {
1902
+ name: "SDK Client",
1903
+ status: "fail",
1904
+ message: "Failed to initialize CodexClient",
1905
+ details: error.message
1906
+ };
1907
+ }
1908
+ }
1909
+ async function checkCache() {
1910
+ try {
1911
+ const client = await getClient();
1912
+ const stats = await client.getCacheStats();
1913
+ if (stats.entryCount === 0) {
1914
+ return {
1915
+ name: "Cache",
1916
+ status: "warn",
1917
+ message: "Cache is empty",
1918
+ details: "Fetch some documents to populate cache"
1919
+ };
1920
+ }
1921
+ const healthPercent = stats.entryCount > 0 ? stats.freshCount / stats.entryCount * 100 : 100;
1922
+ if (healthPercent < 50) {
1923
+ return {
1924
+ name: "Cache",
1925
+ status: "warn",
1926
+ message: `${stats.entryCount} entries (${healthPercent.toFixed(0)}% fresh)`,
1927
+ details: `${stats.expiredCount} expired, ${stats.staleCount} stale`
1928
+ };
1929
+ }
1930
+ return {
1931
+ name: "Cache",
1932
+ status: "pass",
1933
+ message: `${stats.entryCount} entries (${healthPercent.toFixed(0)}% fresh)`,
1934
+ details: `${formatSize4(stats.totalSize)} total`
1935
+ };
1936
+ } catch (error) {
1937
+ return {
1938
+ name: "Cache",
1939
+ status: "fail",
1940
+ message: "Cache check failed",
1941
+ details: error.message
1942
+ };
1943
+ }
1944
+ }
1945
+ async function checkStorage() {
1946
+ const configPath = path3.join(process.cwd(), ".fractary", "codex.yaml");
1947
+ try {
1948
+ const config = await readYamlConfig(configPath);
1949
+ const providers = config.storage || [];
1950
+ if (providers.length === 0) {
1951
+ return {
1952
+ name: "Storage",
1953
+ status: "warn",
1954
+ message: "No storage providers configured",
1955
+ details: "Configure at least one provider in .fractary/codex.yaml"
1956
+ };
1957
+ }
1958
+ const providerTypes = providers.map((p) => p.type).join(", ");
1959
+ const hasGitHub = providers.some((p) => p.type === "github");
1960
+ if (hasGitHub && !process.env.GITHUB_TOKEN) {
1961
+ return {
1962
+ name: "Storage",
1963
+ status: "warn",
1964
+ message: `${providers.length} provider(s): ${providerTypes}`,
1965
+ details: "GITHUB_TOKEN not set (required for GitHub provider)"
1966
+ };
1967
+ }
1968
+ return {
1969
+ name: "Storage",
1970
+ status: "pass",
1971
+ message: `${providers.length} provider(s): ${providerTypes}`,
1972
+ details: "All configured providers available"
1973
+ };
1974
+ } catch (error) {
1975
+ return {
1976
+ name: "Storage",
1977
+ status: "fail",
1978
+ message: "Storage check failed",
1979
+ details: error.message
1980
+ };
1981
+ }
1982
+ }
1983
+ async function checkTypes() {
1984
+ try {
1985
+ const client = await getClient();
1986
+ const registry = client.getTypeRegistry();
1987
+ const allTypes = registry.list();
1988
+ const builtinCount = allTypes.filter((t) => registry.isBuiltIn(t.name)).length;
1989
+ const customCount = allTypes.length - builtinCount;
1990
+ return {
1991
+ name: "Type Registry",
1992
+ status: "pass",
1993
+ message: `${allTypes.length} types registered`,
1994
+ details: `${builtinCount} built-in, ${customCount} custom`
1995
+ };
1996
+ } catch (error) {
1997
+ return {
1998
+ name: "Type Registry",
1999
+ status: "fail",
2000
+ message: "Type registry check failed",
2001
+ details: error.message
2002
+ };
2003
+ }
2004
+ }
2005
+ function formatSize4(bytes) {
2006
+ if (bytes < 1024) return `${bytes} B`;
2007
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
2008
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2009
+ }
2010
+ function healthCommand() {
2011
+ const cmd = new Command("health");
2012
+ cmd.description("Run diagnostics on codex setup").option("--json", "Output as JSON").action(async (options) => {
2013
+ try {
2014
+ const checks = [];
2015
+ checks.push(await checkConfiguration());
2016
+ checks.push(await checkSDKClient());
2017
+ checks.push(await checkCache());
2018
+ checks.push(await checkStorage());
2019
+ checks.push(await checkTypes());
2020
+ const passed = checks.filter((c) => c.status === "pass").length;
2021
+ const warned = checks.filter((c) => c.status === "warn").length;
2022
+ const failed = checks.filter((c) => c.status === "fail").length;
2023
+ if (options.json) {
2024
+ console.log(JSON.stringify({
2025
+ summary: {
2026
+ total: checks.length,
2027
+ passed,
2028
+ warned,
2029
+ failed,
2030
+ healthy: failed === 0
2031
+ },
2032
+ checks
2033
+ }, null, 2));
2034
+ return;
2035
+ }
2036
+ console.log(chalk6.bold("Codex Health Check\n"));
2037
+ for (const check of checks) {
2038
+ const icon = check.status === "pass" ? chalk6.green("\u2713") : check.status === "warn" ? chalk6.yellow("\u26A0") : chalk6.red("\u2717");
2039
+ const statusColor = check.status === "pass" ? chalk6.green : check.status === "warn" ? chalk6.yellow : chalk6.red;
2040
+ console.log(`${icon} ${chalk6.bold(check.name)}`);
2041
+ console.log(` ${statusColor(check.message)}`);
2042
+ if (check.details) {
2043
+ console.log(` ${chalk6.dim(check.details)}`);
2044
+ }
2045
+ console.log("");
2046
+ }
2047
+ console.log(chalk6.dim("\u2500".repeat(60)));
2048
+ const overallStatus = failed > 0 ? chalk6.red("UNHEALTHY") : warned > 0 ? chalk6.yellow("DEGRADED") : chalk6.green("HEALTHY");
2049
+ console.log(`Status: ${overallStatus}`);
2050
+ console.log(chalk6.dim(`${passed} passed, ${warned} warnings, ${failed} failed`));
2051
+ if (failed > 0 || warned > 0) {
2052
+ console.log("");
2053
+ console.log(chalk6.dim("Run checks individually for more details:"));
2054
+ console.log(chalk6.dim(" fractary codex cache stats"));
2055
+ console.log(chalk6.dim(" fractary codex types list"));
2056
+ }
2057
+ if (failed > 0) {
2058
+ process.exit(1);
2059
+ }
2060
+ } catch (error) {
2061
+ console.error(chalk6.red("Error:"), error.message);
2062
+ process.exit(1);
2063
+ }
2064
+ });
2065
+ return cmd;
2066
+ }
2067
+
2068
+ // src/commands/migrate.ts
2069
+ init_esm_shims();
2070
+ init_migrate_config();
2071
+ async function fileExists3(filePath) {
2072
+ try {
2073
+ await fs.access(filePath);
2074
+ return true;
2075
+ } catch {
2076
+ return false;
2077
+ }
2078
+ }
2079
+ async function readFileContent(filePath) {
2080
+ return fs.readFile(filePath, "utf-8");
2081
+ }
2082
+ function migrateCommand() {
2083
+ const cmd = new Command("migrate");
2084
+ cmd.description("Migrate legacy JSON configuration to v3.0 YAML format").option("--dry-run", "Show migration plan without executing").option("--no-backup", "Skip creating backup of old config").option("--json", "Output as JSON").action(async (options) => {
2085
+ try {
2086
+ const legacyConfigPath = path3.join(process.cwd(), ".fractary", "plugins", "codex", "config.json");
2087
+ const newConfigPath = path3.join(process.cwd(), ".fractary", "codex.yaml");
2088
+ if (!await fileExists3(legacyConfigPath)) {
2089
+ if (options.json) {
2090
+ console.log(JSON.stringify({
2091
+ status: "no_config",
2092
+ message: "No legacy configuration file found",
2093
+ path: legacyConfigPath
2094
+ }));
2095
+ } else {
2096
+ console.log(chalk6.yellow("\u26A0 No legacy configuration file found."));
2097
+ console.log(chalk6.dim(` Expected: ${legacyConfigPath}`));
2098
+ console.log(chalk6.dim('\nRun "fractary codex init" to create a new v3.0 YAML configuration.'));
2099
+ }
2100
+ return;
2101
+ }
2102
+ if (await fileExists3(newConfigPath) && !options.dryRun) {
2103
+ if (options.json) {
2104
+ console.log(JSON.stringify({
2105
+ status: "already_migrated",
2106
+ message: "YAML configuration already exists",
2107
+ path: newConfigPath
2108
+ }));
2109
+ } else {
2110
+ console.log(chalk6.yellow("\u26A0 YAML configuration already exists."));
2111
+ console.log(chalk6.dim(` Path: ${newConfigPath}`));
2112
+ console.log(chalk6.dim('\nUse "fractary codex init --force" to recreate.'));
2113
+ }
2114
+ return;
2115
+ }
2116
+ const legacyContent = await readFileContent(legacyConfigPath);
2117
+ let legacyConfig;
2118
+ try {
2119
+ legacyConfig = JSON.parse(legacyContent);
2120
+ } catch {
2121
+ console.error(chalk6.red("Error:"), "Invalid JSON in legacy config file.");
2122
+ process.exit(1);
2123
+ }
2124
+ if (!options.json && !options.dryRun) {
2125
+ console.log(chalk6.blue("Migrating Codex configuration to v3.0 YAML format...\n"));
2126
+ }
2127
+ const migrationResult = await migrateConfig(
2128
+ legacyConfigPath,
2129
+ {
2130
+ createBackup: options.backup !== false,
2131
+ backupSuffix: (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")
2132
+ }
2133
+ );
2134
+ if (!options.json) {
2135
+ console.log(chalk6.bold("Legacy Configuration:"));
2136
+ console.log(chalk6.dim(` Path: ${legacyConfigPath}`));
2137
+ console.log(chalk6.dim(` Organization: ${legacyConfig.organization || legacyConfig.organizationSlug || "unknown"}`));
2138
+ console.log("");
2139
+ console.log(chalk6.bold("Migration Changes:"));
2140
+ console.log(chalk6.green(" + Format: JSON \u2192 YAML"));
2141
+ console.log(chalk6.green(" + Location: .fractary/plugins/codex/ \u2192 .fractary/"));
2142
+ console.log(chalk6.green(" + File: config.json \u2192 codex.yaml"));
2143
+ console.log(chalk6.green(" + Storage: Multi-provider configuration"));
2144
+ console.log(chalk6.green(" + Cache: Modern cache management"));
2145
+ console.log(chalk6.green(" + Types: Custom type registry"));
2146
+ if (migrationResult.warnings.length > 0) {
2147
+ console.log("");
2148
+ console.log(chalk6.yellow("Warnings:"));
2149
+ for (const warning of migrationResult.warnings) {
2150
+ console.log(chalk6.yellow(" \u26A0"), chalk6.dim(warning));
2151
+ }
2152
+ }
2153
+ console.log("");
2154
+ if (options.dryRun) {
2155
+ console.log(chalk6.blue("Dry run - no changes made."));
2156
+ console.log(chalk6.dim("Run without --dry-run to execute migration."));
2157
+ return;
2158
+ }
2159
+ }
2160
+ if (options.json) {
2161
+ const output = {
2162
+ status: options.dryRun ? "migration_ready" : "migrated",
2163
+ dryRun: options.dryRun || false,
2164
+ legacyConfig: {
2165
+ path: legacyConfigPath,
2166
+ organization: legacyConfig.organization || legacyConfig.organizationSlug
2167
+ },
2168
+ newConfig: {
2169
+ path: newConfigPath,
2170
+ organization: migrationResult.yamlConfig.organization
2171
+ },
2172
+ warnings: migrationResult.warnings,
2173
+ backupPath: migrationResult.backupPath
2174
+ };
2175
+ console.log(JSON.stringify(output, null, 2));
2176
+ if (options.dryRun) {
2177
+ return;
2178
+ }
2179
+ }
2180
+ if (!options.dryRun) {
2181
+ await writeYamlConfig(migrationResult.yamlConfig, newConfigPath);
2182
+ const cacheDir = path3.join(process.cwd(), ".codex-cache");
2183
+ await fs.mkdir(cacheDir, { recursive: true });
2184
+ if (!options.json) {
2185
+ console.log(chalk6.green("\u2713"), "YAML configuration created");
2186
+ console.log(chalk6.green("\u2713"), "Cache directory initialized");
2187
+ if (migrationResult.backupPath) {
2188
+ console.log(chalk6.green("\u2713"), "Legacy config backed up");
2189
+ }
2190
+ console.log("");
2191
+ console.log(chalk6.bold("New Configuration:"));
2192
+ console.log(chalk6.dim(` Path: ${newConfigPath}`));
2193
+ console.log(chalk6.dim(` Organization: ${migrationResult.yamlConfig.organization}`));
2194
+ console.log(chalk6.dim(` Cache: ${migrationResult.yamlConfig.cacheDir || ".codex-cache"}`));
2195
+ console.log(chalk6.dim(` Storage Providers: ${migrationResult.yamlConfig.storage?.length || 0}`));
2196
+ console.log("");
2197
+ console.log(chalk6.bold("Next Steps:"));
2198
+ console.log(chalk6.dim(" 1. Review the new configuration: .fractary/codex.yaml"));
2199
+ console.log(chalk6.dim(' 2. Set your GitHub token: export GITHUB_TOKEN="your_token"'));
2200
+ console.log(chalk6.dim(" 3. Test fetching: fractary codex fetch codex://org/project/path"));
2201
+ if (migrationResult.backupPath) {
2202
+ console.log("");
2203
+ console.log(chalk6.dim(`Backup saved: ${path3.basename(migrationResult.backupPath)}`));
2204
+ }
2205
+ }
2206
+ }
2207
+ } catch (error) {
2208
+ if (options.json) {
2209
+ console.log(JSON.stringify({
2210
+ status: "error",
2211
+ message: error.message
2212
+ }));
2213
+ } else {
2214
+ console.error(chalk6.red("Error:"), error.message);
2215
+ }
2216
+ process.exit(1);
2217
+ }
2218
+ });
2219
+ return cmd;
2220
+ }
2221
+
2222
+ // src/cli.ts
2223
+ function createCLI() {
2224
+ const program = new Command("fractary-codex");
2225
+ program.description("Centralized knowledge management and distribution for AI agents").version("0.2.0");
2226
+ program.addCommand(initCommand());
2227
+ program.addCommand(fetchCommand());
2228
+ program.addCommand(cacheCommand());
2229
+ program.addCommand(syncCommand());
2230
+ program.addCommand(typesCommand());
2231
+ program.addCommand(healthCommand());
2232
+ program.addCommand(migrateCommand());
2233
+ return program;
2234
+ }
2235
+ async function main() {
2236
+ try {
2237
+ const program = createCLI();
2238
+ await program.parseAsync(process.argv);
2239
+ } catch (error) {
2240
+ console.error("Error:", error instanceof Error ? error.message : String(error));
2241
+ process.exit(1);
2242
+ }
2243
+ }
2244
+ main();
2245
+ //# sourceMappingURL=cli.js.map
2246
+ //# sourceMappingURL=cli.js.map