@fractary/codex-cli 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import * as path3 from 'path';
2
+ import * as path4 from 'path';
3
3
  import 'url';
4
4
  import * as fs from 'fs/promises';
5
5
  import * as yaml from 'js-yaml';
6
6
  import { ValidationError, PermissionDeniedError, ConfigurationError, CodexError } from '@fractary/codex';
7
7
  import { Command } from 'commander';
8
- import chalk6 from 'chalk';
9
- import { execSync } from 'child_process';
8
+ import chalk8 from 'chalk';
9
+ import * as crypto from 'crypto';
10
10
 
11
11
  var __defProp = Object.defineProperty;
12
12
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -157,7 +157,7 @@ async function migrateConfig(legacyConfigPath, options) {
157
157
  }
158
158
  }
159
159
  async function writeYamlConfig(config, outputPath) {
160
- const dir = path3.dirname(outputPath);
160
+ const dir = path4.dirname(outputPath);
161
161
  await fs.mkdir(dir, { recursive: true });
162
162
  const yamlContent = yaml.dump(config, {
163
163
  indent: 2,
@@ -381,7 +381,7 @@ var init_codex_client = __esm({
381
381
  const { readYamlConfig: readYamlConfig2 } = await Promise.resolve().then(() => (init_migrate_config(), migrate_config_exports));
382
382
  const { resolveEnvVarsInConfig: resolveEnvVarsInConfig2 } = await Promise.resolve().then(() => (init_config_types(), config_types_exports));
383
383
  try {
384
- const configPath = path3.join(process.cwd(), ".fractary", "codex.yaml");
384
+ const configPath = path4.join(process.cwd(), ".fractary", "codex.yaml");
385
385
  let config;
386
386
  try {
387
387
  config = await readYamlConfig2(configPath);
@@ -593,13 +593,110 @@ var init_codex_client = __esm({
593
593
  // src/cli.ts
594
594
  init_esm_shims();
595
595
 
596
- // src/commands/init.ts
596
+ // src/commands/document/index.ts
597
+ init_esm_shims();
598
+
599
+ // src/commands/document/fetch.ts
600
+ init_esm_shims();
601
+
602
+ // src/client/get-client.ts
603
+ init_esm_shims();
604
+ var clientInstance = null;
605
+ async function getClient(options) {
606
+ if (!clientInstance) {
607
+ const { CodexClient: CodexClient2 } = await Promise.resolve().then(() => (init_codex_client(), codex_client_exports));
608
+ clientInstance = await CodexClient2.create(options);
609
+ }
610
+ return clientInstance;
611
+ }
612
+
613
+ // src/commands/document/fetch.ts
614
+ function hashContent(content) {
615
+ return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
616
+ }
617
+ function fetchCommand() {
618
+ const cmd = new Command("fetch");
619
+ 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) => {
620
+ try {
621
+ const { validateUri } = await import('@fractary/codex');
622
+ if (!validateUri(uri)) {
623
+ console.error(chalk8.red("Error: Invalid URI format"));
624
+ console.log(chalk8.dim("Expected: codex://org/project/path/to/file.md"));
625
+ console.log(chalk8.dim("Example: codex://fractary/codex/docs/api.md"));
626
+ process.exit(1);
627
+ }
628
+ const client = await getClient();
629
+ if (!options.json && !options.bypassCache) {
630
+ console.error(chalk8.dim(`Fetching ${uri}...`));
631
+ }
632
+ const result = await client.fetch(uri, {
633
+ bypassCache: options.bypassCache,
634
+ ttl: options.ttl
635
+ });
636
+ if (options.json) {
637
+ const output = {
638
+ uri,
639
+ content: result.content.toString("utf-8"),
640
+ metadata: {
641
+ fromCache: result.fromCache,
642
+ fetchedAt: result.metadata?.fetchedAt,
643
+ expiresAt: result.metadata?.expiresAt,
644
+ contentLength: result.metadata?.contentLength || result.content.length,
645
+ contentHash: hashContent(result.content)
646
+ }
647
+ };
648
+ console.log(JSON.stringify(output, null, 2));
649
+ } else if (options.output) {
650
+ await fs.writeFile(options.output, result.content);
651
+ console.log(chalk8.green("\u2713"), `Written to ${options.output}`);
652
+ console.log(chalk8.dim(` Size: ${result.content.length} bytes`));
653
+ if (result.fromCache) {
654
+ console.log(chalk8.dim(" Source: cache"));
655
+ } else {
656
+ console.log(chalk8.dim(" Source: storage"));
657
+ }
658
+ } else {
659
+ if (result.fromCache && !options.bypassCache) {
660
+ console.error(chalk8.green("\u2713"), chalk8.dim("from cache\n"));
661
+ } else {
662
+ console.error(chalk8.green("\u2713"), chalk8.dim("fetched\n"));
663
+ }
664
+ console.log(result.content.toString("utf-8"));
665
+ }
666
+ } catch (error) {
667
+ console.error(chalk8.red("Error:"), error.message);
668
+ if (error.message.includes("Failed to load configuration")) {
669
+ console.log(chalk8.dim('\nRun "fractary codex init" to create a configuration.'));
670
+ } else if (error.message.includes("GITHUB_TOKEN")) {
671
+ console.log(chalk8.dim('\nSet your GitHub token: export GITHUB_TOKEN="your_token"'));
672
+ } else if (error.message.includes("not found") || error.message.includes("404")) {
673
+ console.log(chalk8.dim("\nThe document may not exist or you may not have access."));
674
+ console.log(chalk8.dim("Check the URI and ensure your storage providers are configured correctly."));
675
+ }
676
+ process.exit(1);
677
+ }
678
+ });
679
+ return cmd;
680
+ }
681
+
682
+ // src/commands/document/index.ts
683
+ function documentCommand() {
684
+ const cmd = new Command("document");
685
+ cmd.description("Manage document operations");
686
+ cmd.addCommand(fetchCommand());
687
+ return cmd;
688
+ }
689
+
690
+ // src/commands/config/index.ts
691
+ init_esm_shims();
692
+
693
+ // src/commands/config/init.ts
597
694
  init_esm_shims();
598
695
  init_migrate_config();
599
696
  async function getOrgFromGitRemote() {
600
697
  try {
601
- const { execSync: execSync2 } = __require("child_process");
602
- const remote = execSync2("git remote get-url origin 2>/dev/null", { encoding: "utf-8" }).trim();
698
+ const { execSync } = __require("child_process");
699
+ const remote = execSync("git remote get-url origin 2>/dev/null", { encoding: "utf-8" }).trim();
603
700
  const sshMatch = remote.match(/git@github\.com:([^/]+)\//);
604
701
  const httpsMatch = remote.match(/github\.com\/([^/]+)\//);
605
702
  return sshMatch?.[1] || httpsMatch?.[1] || null;
@@ -619,7 +716,7 @@ function initCommand() {
619
716
  const cmd = new Command("init");
620
717
  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
718
  try {
622
- console.log(chalk6.blue("Initializing Codex v3.0 (YAML format)...\n"));
719
+ console.log(chalk8.blue("Initializing Codex v3.0 (YAML format)...\n"));
623
720
  let org = options.org;
624
721
  if (!org) {
625
722
  org = await getOrgFromGitRemote();
@@ -628,32 +725,32 @@ function initCommand() {
628
725
  try {
629
726
  const { resolveOrganization } = await import('@fractary/codex');
630
727
  org = resolveOrganization({
631
- repoName: path3.basename(process.cwd())
728
+ repoName: path4.basename(process.cwd())
632
729
  });
633
730
  } catch {
634
731
  }
635
732
  }
636
733
  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"));
734
+ org = path4.basename(process.cwd()).split("-")[0] || "default";
735
+ console.log(chalk8.yellow(`\u26A0 Could not detect organization, using: ${org}`));
736
+ console.log(chalk8.dim(" Use --org <slug> to specify explicitly\n"));
640
737
  } else {
641
- console.log(chalk6.dim(`Organization: ${chalk6.cyan(org)}
738
+ console.log(chalk8.dim(`Organization: ${chalk8.cyan(org)}
642
739
  `));
643
740
  }
644
- const configDir = path3.join(process.cwd(), ".fractary");
645
- const configPath = path3.join(configDir, "codex.yaml");
741
+ const configDir = path4.join(process.cwd(), ".fractary");
742
+ const configPath = path4.join(configDir, "codex.yaml");
646
743
  const configExists = await fileExists(configPath);
647
- const legacyConfigPath = path3.join(process.cwd(), ".fractary", "plugins", "codex", "config.json");
744
+ const legacyConfigPath = path4.join(process.cwd(), ".fractary", "plugins", "codex", "config.json");
648
745
  const legacyExists = await fileExists(legacyConfigPath);
649
746
  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"));
747
+ console.log(chalk8.yellow("\u26A0 Configuration already exists at .fractary/codex.yaml"));
748
+ console.log(chalk8.dim("Use --force to overwrite"));
652
749
  process.exit(1);
653
750
  }
654
751
  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'));
752
+ console.log(chalk8.yellow("\u26A0 Legacy configuration detected at .fractary/plugins/codex/config.json"));
753
+ console.log(chalk8.dim('Run "fractary codex migrate" to upgrade to YAML format\n'));
657
754
  }
658
755
  console.log("Creating directory structure...");
659
756
  const dirs = [
@@ -661,8 +758,8 @@ function initCommand() {
661
758
  ".codex-cache"
662
759
  ];
663
760
  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 + "/"));
761
+ await fs.mkdir(path4.join(process.cwd(), dir), { recursive: true });
762
+ console.log(chalk8.green("\u2713"), chalk8.dim(dir + "/"));
666
763
  }
667
764
  console.log("\nCreating YAML configuration...");
668
765
  const config = getDefaultYamlConfig(org);
@@ -670,113 +767,183 @@ function initCommand() {
670
767
  config.mcp.enabled = true;
671
768
  }
672
769
  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`));
770
+ console.log(chalk8.green("\u2713"), chalk8.dim(".fractary/codex.yaml"));
771
+ console.log(chalk8.green("\n\u2713 Codex v3.0 initialized successfully!\n"));
772
+ console.log(chalk8.bold("Configuration:"));
773
+ console.log(chalk8.dim(` Organization: ${org}`));
774
+ console.log(chalk8.dim(` Cache: .codex-cache/`));
775
+ console.log(chalk8.dim(` Config: .fractary/codex.yaml`));
679
776
  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"));
777
+ console.log(chalk8.dim(` MCP Server: Enabled (port 3000)`));
778
+ }
779
+ console.log(chalk8.bold("\nStorage providers configured:"));
780
+ console.log(chalk8.dim(" - Local filesystem (./knowledge)"));
781
+ console.log(chalk8.dim(" - GitHub (requires GITHUB_TOKEN)"));
782
+ console.log(chalk8.dim(" - HTTP endpoint"));
783
+ console.log(chalk8.bold("\nNext steps:"));
784
+ console.log(chalk8.dim(' 1. Set your GitHub token: export GITHUB_TOKEN="your_token"'));
785
+ console.log(chalk8.dim(" 2. Edit .fractary/codex.yaml to configure storage providers"));
786
+ console.log(chalk8.dim(" 3. Fetch a document: fractary codex fetch codex://org/project/path"));
787
+ console.log(chalk8.dim(" 4. Check cache: fractary codex cache list"));
691
788
  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'));
789
+ console.log(chalk8.yellow("\n\u26A0 Legacy config detected:"));
790
+ console.log(chalk8.dim(' Run "fractary codex migrate" to convert your existing config'));
694
791
  }
695
792
  } catch (error) {
696
- console.error(chalk6.red("Error:"), error.message);
793
+ console.error(chalk8.red("Error:"), error.message);
697
794
  process.exit(1);
698
795
  }
699
796
  });
700
797
  return cmd;
701
798
  }
702
799
 
703
- // src/commands/fetch.ts
704
- init_esm_shims();
705
-
706
- // src/client/get-client.ts
800
+ // src/commands/config/migrate.ts
707
801
  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);
802
+ init_migrate_config();
803
+ async function fileExists2(filePath) {
804
+ try {
805
+ await fs.access(filePath);
806
+ return true;
807
+ } catch {
808
+ return false;
713
809
  }
714
- return clientInstance;
715
810
  }
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);
811
+ async function readFileContent(filePath) {
812
+ return fs.readFile(filePath, "utf-8");
721
813
  }
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) => {
814
+ function migrateCommand() {
815
+ const cmd = new Command("migrate");
816
+ 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) => {
725
817
  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"));
818
+ const legacyConfigPath = path4.join(process.cwd(), ".fractary", "plugins", "codex", "config.json");
819
+ const newConfigPath = path4.join(process.cwd(), ".fractary", "codex.yaml");
820
+ if (!await fileExists2(legacyConfigPath)) {
821
+ if (options.json) {
822
+ console.log(JSON.stringify({
823
+ status: "no_config",
824
+ message: "No legacy configuration file found",
825
+ path: legacyConfigPath
826
+ }));
827
+ } else {
828
+ console.log(chalk8.yellow("\u26A0 No legacy configuration file found."));
829
+ console.log(chalk8.dim(` Expected: ${legacyConfigPath}`));
830
+ console.log(chalk8.dim('\nRun "fractary codex init" to create a new v3.0 YAML configuration.'));
831
+ }
832
+ return;
833
+ }
834
+ if (await fileExists2(newConfigPath) && !options.dryRun) {
835
+ if (options.json) {
836
+ console.log(JSON.stringify({
837
+ status: "already_migrated",
838
+ message: "YAML configuration already exists",
839
+ path: newConfigPath
840
+ }));
841
+ } else {
842
+ console.log(chalk8.yellow("\u26A0 YAML configuration already exists."));
843
+ console.log(chalk8.dim(` Path: ${newConfigPath}`));
844
+ console.log(chalk8.dim('\nUse "fractary codex init --force" to recreate.'));
845
+ }
846
+ return;
847
+ }
848
+ const legacyContent = await readFileContent(legacyConfigPath);
849
+ let legacyConfig;
850
+ try {
851
+ legacyConfig = JSON.parse(legacyContent);
852
+ } catch {
853
+ console.error(chalk8.red("Error:"), "Invalid JSON in legacy config file.");
731
854
  process.exit(1);
732
855
  }
733
- const client = await getClient();
734
- if (!options.json && !options.bypassCache) {
735
- console.error(chalk6.dim(`Fetching ${uri}...`));
856
+ if (!options.json && !options.dryRun) {
857
+ console.log(chalk8.blue("Migrating Codex configuration to v3.0 YAML format...\n"));
858
+ }
859
+ const migrationResult = await migrateConfig(
860
+ legacyConfigPath,
861
+ {
862
+ createBackup: options.backup !== false,
863
+ backupSuffix: (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")
864
+ }
865
+ );
866
+ if (!options.json) {
867
+ console.log(chalk8.bold("Legacy Configuration:"));
868
+ console.log(chalk8.dim(` Path: ${legacyConfigPath}`));
869
+ console.log(chalk8.dim(` Organization: ${legacyConfig.organization || legacyConfig.organizationSlug || "unknown"}`));
870
+ console.log("");
871
+ console.log(chalk8.bold("Migration Changes:"));
872
+ console.log(chalk8.green(" + Format: JSON \u2192 YAML"));
873
+ console.log(chalk8.green(" + Location: .fractary/plugins/codex/ \u2192 .fractary/"));
874
+ console.log(chalk8.green(" + File: config.json \u2192 codex.yaml"));
875
+ console.log(chalk8.green(" + Storage: Multi-provider configuration"));
876
+ console.log(chalk8.green(" + Cache: Modern cache management"));
877
+ console.log(chalk8.green(" + Types: Custom type registry"));
878
+ if (migrationResult.warnings.length > 0) {
879
+ console.log("");
880
+ console.log(chalk8.yellow("Warnings:"));
881
+ for (const warning of migrationResult.warnings) {
882
+ console.log(chalk8.yellow(" \u26A0"), chalk8.dim(warning));
883
+ }
884
+ }
885
+ console.log("");
886
+ if (options.dryRun) {
887
+ console.log(chalk8.blue("Dry run - no changes made."));
888
+ console.log(chalk8.dim("Run without --dry-run to execute migration."));
889
+ return;
890
+ }
736
891
  }
737
- const result = await client.fetch(uri, {
738
- bypassCache: options.bypassCache,
739
- ttl: options.ttl
740
- });
741
892
  if (options.json) {
742
893
  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
- }
894
+ status: options.dryRun ? "migration_ready" : "migrated",
895
+ dryRun: options.dryRun || false,
896
+ legacyConfig: {
897
+ path: legacyConfigPath,
898
+ organization: legacyConfig.organization || legacyConfig.organizationSlug
899
+ },
900
+ newConfig: {
901
+ path: newConfigPath,
902
+ organization: migrationResult.yamlConfig.organization
903
+ },
904
+ warnings: migrationResult.warnings,
905
+ backupPath: migrationResult.backupPath
752
906
  };
753
907
  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"));
908
+ if (options.dryRun) {
909
+ return;
762
910
  }
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"));
911
+ }
912
+ if (!options.dryRun) {
913
+ await writeYamlConfig(migrationResult.yamlConfig, newConfigPath);
914
+ const cacheDir = path4.join(process.cwd(), ".codex-cache");
915
+ await fs.mkdir(cacheDir, { recursive: true });
916
+ if (!options.json) {
917
+ console.log(chalk8.green("\u2713"), "YAML configuration created");
918
+ console.log(chalk8.green("\u2713"), "Cache directory initialized");
919
+ if (migrationResult.backupPath) {
920
+ console.log(chalk8.green("\u2713"), "Legacy config backed up");
921
+ }
922
+ console.log("");
923
+ console.log(chalk8.bold("New Configuration:"));
924
+ console.log(chalk8.dim(` Path: ${newConfigPath}`));
925
+ console.log(chalk8.dim(` Organization: ${migrationResult.yamlConfig.organization}`));
926
+ console.log(chalk8.dim(` Cache: ${migrationResult.yamlConfig.cacheDir || ".codex-cache"}`));
927
+ console.log(chalk8.dim(` Storage Providers: ${migrationResult.yamlConfig.storage?.length || 0}`));
928
+ console.log("");
929
+ console.log(chalk8.bold("Next Steps:"));
930
+ console.log(chalk8.dim(" 1. Review the new configuration: .fractary/codex.yaml"));
931
+ console.log(chalk8.dim(' 2. Set your GitHub token: export GITHUB_TOKEN="your_token"'));
932
+ console.log(chalk8.dim(" 3. Test fetching: fractary codex fetch codex://org/project/path"));
933
+ if (migrationResult.backupPath) {
934
+ console.log("");
935
+ console.log(chalk8.dim(`Backup saved: ${path4.basename(migrationResult.backupPath)}`));
936
+ }
768
937
  }
769
- console.log(result.content.toString("utf-8"));
770
938
  }
771
939
  } 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."));
940
+ if (options.json) {
941
+ console.log(JSON.stringify({
942
+ status: "error",
943
+ message: error.message
944
+ }));
945
+ } else {
946
+ console.error(chalk8.red("Error:"), error.message);
780
947
  }
781
948
  process.exit(1);
782
949
  }
@@ -784,6 +951,15 @@ function fetchCommand() {
784
951
  return cmd;
785
952
  }
786
953
 
954
+ // src/commands/config/index.ts
955
+ function configCommand() {
956
+ const cmd = new Command("config");
957
+ cmd.description("Manage configuration");
958
+ cmd.addCommand(initCommand());
959
+ cmd.addCommand(migrateCommand());
960
+ return cmd;
961
+ }
962
+
787
963
  // src/commands/cache/index.ts
788
964
  init_esm_shims();
789
965
 
@@ -804,8 +980,8 @@ function cacheListCommand() {
804
980
  if (options.json) {
805
981
  console.log(JSON.stringify({ entries: 0, message: "Cache is empty" }));
806
982
  } else {
807
- console.log(chalk6.yellow("Cache is empty."));
808
- console.log(chalk6.dim("Fetch some documents to populate the cache."));
983
+ console.log(chalk8.yellow("Cache is empty."));
984
+ console.log(chalk8.dim("Fetch some documents to populate the cache."));
809
985
  }
810
986
  return;
811
987
  }
@@ -819,25 +995,25 @@ function cacheListCommand() {
819
995
  }, null, 2));
820
996
  return;
821
997
  }
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`);
998
+ console.log(chalk8.bold("Cache Overview\n"));
999
+ console.log(chalk8.bold("Entries:"));
1000
+ console.log(` Total: ${chalk8.cyan(stats.entryCount.toString())} entries`);
1001
+ console.log(` Fresh: ${chalk8.green(stats.freshCount.toString())} entries`);
1002
+ console.log(` Stale: ${stats.staleCount > 0 ? chalk8.yellow(stats.staleCount.toString()) : chalk8.dim("0")} entries`);
1003
+ console.log(` Expired: ${stats.expiredCount > 0 ? chalk8.red(stats.expiredCount.toString()) : chalk8.dim("0")} entries`);
828
1004
  console.log("");
829
- console.log(chalk6.bold("Storage:"));
830
- console.log(` Total size: ${chalk6.cyan(formatSize(stats.totalSize))}`);
1005
+ console.log(chalk8.bold("Storage:"));
1006
+ console.log(` Total size: ${chalk8.cyan(formatSize(stats.totalSize))}`);
831
1007
  console.log("");
832
1008
  const healthPercent = stats.entryCount > 0 ? stats.freshCount / stats.entryCount * 100 : 100;
833
- const healthColor = healthPercent > 80 ? chalk6.green : healthPercent > 50 ? chalk6.yellow : chalk6.red;
1009
+ const healthColor = healthPercent > 80 ? chalk8.green : healthPercent > 50 ? chalk8.yellow : chalk8.red;
834
1010
  console.log(`Cache health: ${healthColor(`${healthPercent.toFixed(0)}% fresh`)}`);
835
1011
  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.'));
1012
+ console.log(chalk8.dim("Note: Individual cache entries are managed by the SDK."));
1013
+ console.log(chalk8.dim('Use "fractary codex cache stats" for detailed statistics.'));
1014
+ console.log(chalk8.dim('Use "fractary codex cache clear" to clear cache entries.'));
839
1015
  } catch (error) {
840
- console.error(chalk6.red("Error:"), error.message);
1016
+ console.error(chalk8.red("Error:"), error.message);
841
1017
  process.exit(1);
842
1018
  }
843
1019
  });
@@ -853,7 +1029,7 @@ function cacheClearCommand() {
853
1029
  const client = await getClient();
854
1030
  const statsBefore = await client.getCacheStats();
855
1031
  if (statsBefore.entryCount === 0) {
856
- console.log(chalk6.yellow("Cache is already empty. Nothing to clear."));
1032
+ console.log(chalk8.yellow("Cache is already empty. Nothing to clear."));
857
1033
  return;
858
1034
  }
859
1035
  let pattern;
@@ -862,45 +1038,45 @@ function cacheClearCommand() {
862
1038
  } else if (options.pattern) {
863
1039
  pattern = options.pattern;
864
1040
  } 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/*")'));
1041
+ console.log(chalk8.yellow("Please specify what to clear:"));
1042
+ console.log(chalk8.dim(" --all Clear entire cache"));
1043
+ console.log(chalk8.dim(' --pattern Clear entries matching pattern (e.g., "codex://fractary/*")'));
868
1044
  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/*"'));
1045
+ console.log(chalk8.dim("Examples:"));
1046
+ console.log(chalk8.dim(" fractary codex cache clear --all"));
1047
+ console.log(chalk8.dim(' fractary codex cache clear --pattern "codex://fractary/cli/*"'));
872
1048
  return;
873
1049
  }
874
1050
  if (options.dryRun) {
875
- console.log(chalk6.blue("Dry run - would clear:\n"));
1051
+ console.log(chalk8.blue("Dry run - would clear:\n"));
876
1052
  if (pattern) {
877
- console.log(chalk6.dim(` Pattern: ${pattern}`));
878
- console.log(chalk6.dim(` This would invalidate matching cache entries`));
1053
+ console.log(chalk8.dim(` Pattern: ${pattern}`));
1054
+ console.log(chalk8.dim(` This would invalidate matching cache entries`));
879
1055
  } else {
880
- console.log(chalk6.dim(` All cache entries (${statsBefore.entryCount} entries)`));
1056
+ console.log(chalk8.dim(` All cache entries (${statsBefore.entryCount} entries)`));
881
1057
  }
882
- console.log(chalk6.dim(`
1058
+ console.log(chalk8.dim(`
883
1059
  Total size: ${formatSize2(statsBefore.totalSize)}`));
884
1060
  return;
885
1061
  }
886
1062
  if (pattern) {
887
- console.log(chalk6.blue(`Clearing cache entries matching pattern: ${pattern}
1063
+ console.log(chalk8.blue(`Clearing cache entries matching pattern: ${pattern}
888
1064
  `));
889
1065
  await client.invalidateCache(pattern);
890
1066
  } else {
891
- console.log(chalk6.blue(`Clearing entire cache (${statsBefore.entryCount} entries)...
1067
+ console.log(chalk8.blue(`Clearing entire cache (${statsBefore.entryCount} entries)...
892
1068
  `));
893
1069
  await client.invalidateCache();
894
1070
  }
895
1071
  const statsAfter = await client.getCacheStats();
896
1072
  const entriesCleared = statsBefore.entryCount - statsAfter.entryCount;
897
1073
  const sizeFreed = statsBefore.totalSize - statsAfter.totalSize;
898
- console.log(chalk6.green(`\u2713 Cleared ${entriesCleared} entries (${formatSize2(sizeFreed)} freed)`));
1074
+ console.log(chalk8.green(`\u2713 Cleared ${entriesCleared} entries (${formatSize2(sizeFreed)} freed)`));
899
1075
  if (statsAfter.entryCount > 0) {
900
- console.log(chalk6.dim(` Remaining: ${statsAfter.entryCount} entries (${formatSize2(statsAfter.totalSize)})`));
1076
+ console.log(chalk8.dim(` Remaining: ${statsAfter.entryCount} entries (${formatSize2(statsAfter.totalSize)})`));
901
1077
  }
902
1078
  } catch (error) {
903
- console.error(chalk6.red("Error:"), error.message);
1079
+ console.error(chalk8.red("Error:"), error.message);
904
1080
  process.exit(1);
905
1081
  }
906
1082
  });
@@ -929,93 +1105,333 @@ function cacheStatsCommand() {
929
1105
  console.log(JSON.stringify(stats, null, 2));
930
1106
  return;
931
1107
  }
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")}`);
1108
+ console.log(chalk8.bold("Cache Statistics\n"));
1109
+ console.log(chalk8.bold("Overview"));
1110
+ console.log(` Total entries: ${chalk8.cyan(stats.entryCount.toString())}`);
1111
+ console.log(` Total size: ${chalk8.cyan(formatSize3(stats.totalSize))}`);
1112
+ console.log(` Fresh entries: ${chalk8.green(stats.freshCount.toString())}`);
1113
+ console.log(` Stale entries: ${stats.staleCount > 0 ? chalk8.yellow(stats.staleCount.toString()) : chalk8.dim("0")}`);
1114
+ console.log(` Expired entries: ${stats.expiredCount > 0 ? chalk8.red(stats.expiredCount.toString()) : chalk8.dim("0")}`);
939
1115
  console.log("");
940
1116
  const healthPercent = stats.entryCount > 0 ? stats.freshCount / stats.entryCount * 100 : 100;
941
- const healthColor = healthPercent > 80 ? chalk6.green : healthPercent > 50 ? chalk6.yellow : chalk6.red;
1117
+ const healthColor = healthPercent > 80 ? chalk8.green : healthPercent > 50 ? chalk8.yellow : chalk8.red;
942
1118
  console.log(`Cache health: ${healthColor(`${healthPercent.toFixed(0)}% fresh`)}`);
943
1119
  if (stats.expiredCount > 0) {
944
- console.log(chalk6.dim('\nRun "fractary codex cache clear --pattern <pattern>" to clean up entries.'));
1120
+ console.log(chalk8.dim('\nRun "fractary codex cache clear --pattern <pattern>" to clean up entries.'));
945
1121
  }
946
1122
  if (stats.entryCount === 0) {
947
- console.log(chalk6.dim("\nNo cached entries. Fetch some documents to populate the cache."));
1123
+ console.log(chalk8.dim("\nNo cached entries. Fetch some documents to populate the cache."));
948
1124
  }
949
1125
  } catch (error) {
950
- console.error(chalk6.red("Error:"), error.message);
1126
+ console.error(chalk8.red("Error:"), error.message);
951
1127
  process.exit(1);
952
1128
  }
953
1129
  });
954
1130
  return cmd;
955
1131
  }
956
1132
 
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
1133
+ // src/commands/cache/health.ts
971
1134
  init_esm_shims();
972
1135
  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`;
1136
+ async function fileExists3(filePath) {
1137
+ try {
1138
+ await fs.access(filePath);
1139
+ return true;
1140
+ } catch {
1141
+ return false;
1142
+ }
990
1143
  }
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();
1144
+ async function checkConfiguration() {
1145
+ const configPath = path4.join(process.cwd(), ".fractary", "codex.yaml");
1146
+ const legacyConfigPath = path4.join(process.cwd(), ".fractary", "plugins", "codex", "config.json");
1147
+ try {
1148
+ if (!await fileExists3(configPath)) {
1149
+ if (await fileExists3(legacyConfigPath)) {
1150
+ return {
1151
+ name: "Configuration",
1152
+ status: "warn",
1153
+ message: "Legacy JSON configuration detected",
1154
+ details: 'Run "fractary codex migrate" to upgrade to YAML format'
1155
+ };
1156
+ }
1157
+ return {
1158
+ name: "Configuration",
1159
+ status: "fail",
1160
+ message: "No configuration found",
1161
+ details: 'Run "fractary codex init" to create configuration'
1162
+ };
1163
+ }
1164
+ const config = await readYamlConfig(configPath);
1165
+ if (!config.organization) {
1166
+ return {
1167
+ name: "Configuration",
1168
+ status: "warn",
1169
+ message: "No organization configured",
1170
+ details: "Organization slug is required"
1171
+ };
1172
+ }
1173
+ const providerCount = config.storage?.length || 0;
1174
+ if (providerCount === 0) {
1175
+ return {
1176
+ name: "Configuration",
1177
+ status: "warn",
1178
+ message: "No storage providers configured",
1179
+ details: "At least one storage provider is recommended"
1180
+ };
1181
+ }
1182
+ return {
1183
+ name: "Configuration",
1184
+ status: "pass",
1185
+ message: "Valid YAML configuration",
1186
+ details: `Organization: ${config.organization}, ${providerCount} storage provider(s)`
1187
+ };
1188
+ } catch (error) {
1189
+ return {
1190
+ name: "Configuration",
1191
+ status: "fail",
1192
+ message: "Invalid configuration",
1193
+ details: error.message
1194
+ };
1195
+ }
1196
+ }
1197
+ async function checkSDKClient() {
1198
+ try {
1199
+ const client = await getClient();
1200
+ const organization = client.getOrganization();
1201
+ return {
1202
+ name: "SDK Client",
1203
+ status: "pass",
1204
+ message: "CodexClient initialized successfully",
1205
+ details: `Organization: ${organization}`
1206
+ };
1207
+ } catch (error) {
1208
+ return {
1209
+ name: "SDK Client",
1210
+ status: "fail",
1211
+ message: "Failed to initialize CodexClient",
1212
+ details: error.message
1213
+ };
1214
+ }
1215
+ }
1216
+ async function checkCache() {
1217
+ try {
1218
+ const client = await getClient();
1219
+ const stats = await client.getCacheStats();
1220
+ if (stats.entryCount === 0) {
1221
+ return {
1222
+ name: "Cache",
1223
+ status: "warn",
1224
+ message: "Cache is empty",
1225
+ details: "Fetch some documents to populate cache"
1226
+ };
1227
+ }
1228
+ const healthPercent = stats.entryCount > 0 ? stats.freshCount / stats.entryCount * 100 : 100;
1229
+ if (healthPercent < 50) {
1230
+ return {
1231
+ name: "Cache",
1232
+ status: "warn",
1233
+ message: `${stats.entryCount} entries (${healthPercent.toFixed(0)}% fresh)`,
1234
+ details: `${stats.expiredCount} expired, ${stats.staleCount} stale`
1235
+ };
1236
+ }
1237
+ return {
1238
+ name: "Cache",
1239
+ status: "pass",
1240
+ message: `${stats.entryCount} entries (${healthPercent.toFixed(0)}% fresh)`,
1241
+ details: `${formatSize4(stats.totalSize)} total`
1242
+ };
1243
+ } catch (error) {
1244
+ return {
1245
+ name: "Cache",
1246
+ status: "fail",
1247
+ message: "Cache check failed",
1248
+ details: error.message
1249
+ };
1250
+ }
1251
+ }
1252
+ async function checkStorage() {
1253
+ const configPath = path4.join(process.cwd(), ".fractary", "codex.yaml");
1254
+ try {
1255
+ const config = await readYamlConfig(configPath);
1256
+ const providers = config.storage || [];
1257
+ if (providers.length === 0) {
1258
+ return {
1259
+ name: "Storage",
1260
+ status: "warn",
1261
+ message: "No storage providers configured",
1262
+ details: "Configure at least one provider in .fractary/codex.yaml"
1263
+ };
1264
+ }
1265
+ const providerTypes = providers.map((p) => p.type).join(", ");
1266
+ const hasGitHub = providers.some((p) => p.type === "github");
1267
+ if (hasGitHub && !process.env.GITHUB_TOKEN) {
1268
+ return {
1269
+ name: "Storage",
1270
+ status: "warn",
1271
+ message: `${providers.length} provider(s): ${providerTypes}`,
1272
+ details: "GITHUB_TOKEN not set (required for GitHub provider)"
1273
+ };
1274
+ }
1275
+ return {
1276
+ name: "Storage",
1277
+ status: "pass",
1278
+ message: `${providers.length} provider(s): ${providerTypes}`,
1279
+ details: "All configured providers available"
1280
+ };
1281
+ } catch (error) {
1282
+ return {
1283
+ name: "Storage",
1284
+ status: "fail",
1285
+ message: "Storage check failed",
1286
+ details: error.message
1287
+ };
1288
+ }
1289
+ }
1290
+ async function checkTypes() {
1291
+ try {
1292
+ const client = await getClient();
1293
+ const registry = client.getTypeRegistry();
1294
+ const allTypes = registry.list();
1295
+ const builtinCount = allTypes.filter((t) => registry.isBuiltIn(t.name)).length;
1296
+ const customCount = allTypes.length - builtinCount;
1297
+ return {
1298
+ name: "Type Registry",
1299
+ status: "pass",
1300
+ message: `${allTypes.length} types registered`,
1301
+ details: `${builtinCount} built-in, ${customCount} custom`
1302
+ };
1303
+ } catch (error) {
1304
+ return {
1305
+ name: "Type Registry",
1306
+ status: "fail",
1307
+ message: "Type registry check failed",
1308
+ details: error.message
1309
+ };
1310
+ }
1311
+ }
1312
+ function formatSize4(bytes) {
1313
+ if (bytes < 1024) return `${bytes} B`;
1314
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1315
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1316
+ }
1317
+ function healthCommand() {
1318
+ const cmd = new Command("health");
1319
+ cmd.description("Run diagnostics on codex setup").option("--json", "Output as JSON").action(async (options) => {
1320
+ try {
1321
+ const checks = [];
1322
+ checks.push(await checkConfiguration());
1323
+ checks.push(await checkSDKClient());
1324
+ checks.push(await checkCache());
1325
+ checks.push(await checkStorage());
1326
+ checks.push(await checkTypes());
1327
+ const passed = checks.filter((c) => c.status === "pass").length;
1328
+ const warned = checks.filter((c) => c.status === "warn").length;
1329
+ const failed = checks.filter((c) => c.status === "fail").length;
1330
+ if (options.json) {
1331
+ console.log(JSON.stringify({
1332
+ summary: {
1333
+ total: checks.length,
1334
+ passed,
1335
+ warned,
1336
+ failed,
1337
+ healthy: failed === 0
1338
+ },
1339
+ checks
1340
+ }, null, 2));
1341
+ return;
1342
+ }
1343
+ console.log(chalk8.bold("Codex Health Check\n"));
1344
+ for (const check of checks) {
1345
+ const icon = check.status === "pass" ? chalk8.green("\u2713") : check.status === "warn" ? chalk8.yellow("\u26A0") : chalk8.red("\u2717");
1346
+ const statusColor = check.status === "pass" ? chalk8.green : check.status === "warn" ? chalk8.yellow : chalk8.red;
1347
+ console.log(`${icon} ${chalk8.bold(check.name)}`);
1348
+ console.log(` ${statusColor(check.message)}`);
1349
+ if (check.details) {
1350
+ console.log(` ${chalk8.dim(check.details)}`);
1351
+ }
1352
+ console.log("");
1353
+ }
1354
+ console.log(chalk8.dim("\u2500".repeat(60)));
1355
+ const overallStatus = failed > 0 ? chalk8.red("UNHEALTHY") : warned > 0 ? chalk8.yellow("DEGRADED") : chalk8.green("HEALTHY");
1356
+ console.log(`Status: ${overallStatus}`);
1357
+ console.log(chalk8.dim(`${passed} passed, ${warned} warnings, ${failed} failed`));
1358
+ if (failed > 0 || warned > 0) {
1359
+ console.log("");
1360
+ console.log(chalk8.dim("Run checks individually for more details:"));
1361
+ console.log(chalk8.dim(" fractary codex cache stats"));
1362
+ console.log(chalk8.dim(" fractary codex types list"));
1363
+ }
1364
+ if (failed > 0) {
1365
+ process.exit(1);
1366
+ }
1367
+ } catch (error) {
1368
+ console.error(chalk8.red("Error:"), error.message);
1369
+ process.exit(1);
1370
+ }
1371
+ });
1372
+ return cmd;
1373
+ }
1374
+
1375
+ // src/commands/cache/index.ts
1376
+ function cacheCommand() {
1377
+ const cmd = new Command("cache");
1378
+ cmd.description("Manage the codex document cache");
1379
+ cmd.addCommand(cacheListCommand());
1380
+ cmd.addCommand(cacheClearCommand());
1381
+ cmd.addCommand(cacheStatsCommand());
1382
+ cmd.addCommand(healthCommand());
1383
+ return cmd;
1384
+ }
1385
+
1386
+ // src/commands/sync.ts
1387
+ init_esm_shims();
1388
+ init_migrate_config();
1389
+ function getEnvironmentBranch(config, env) {
1390
+ const envMap = config.sync?.environments || {
1391
+ dev: "develop",
1392
+ test: "test",
1393
+ staging: "staging",
1394
+ prod: "main"
1395
+ };
1396
+ return envMap[env] || env;
1397
+ }
1398
+ function formatBytes(bytes) {
1399
+ if (bytes < 1024) return `${bytes} B`;
1400
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1401
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1402
+ }
1403
+ function formatDuration(ms) {
1404
+ if (ms < 1e3) return `${ms}ms`;
1405
+ return `${(ms / 1e3).toFixed(1)}s`;
1406
+ }
1407
+ function syncCommand() {
1408
+ const cmd = new Command("sync");
1409
+ 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) => {
1410
+ try {
1411
+ const configPath = path4.join(process.cwd(), ".fractary", "codex.yaml");
1412
+ let config;
1413
+ try {
1414
+ config = await readYamlConfig(configPath);
1415
+ } catch (error) {
1416
+ console.error(chalk8.red("Error:"), "Codex not initialized.");
1417
+ console.log(chalk8.dim('Run "fractary codex init" first.'));
1418
+ process.exit(1);
1419
+ }
1420
+ const { createSyncManager, createLocalStorage, detectCurrentProject } = await import('@fractary/codex');
1421
+ let projectName = name;
1422
+ if (!projectName) {
1423
+ const detected = detectCurrentProject();
1008
1424
  projectName = detected.project || null;
1009
1425
  }
1010
1426
  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."));
1427
+ console.error(chalk8.red("Error:"), "Could not determine project name.");
1428
+ console.log(chalk8.dim("Provide project name as argument or run from a git repository."));
1013
1429
  process.exit(1);
1014
1430
  }
1015
1431
  const validDirections = ["to-codex", "from-codex", "bidirectional"];
1016
1432
  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"));
1433
+ console.error(chalk8.red("Error:"), `Invalid direction: ${options.direction}`);
1434
+ console.log(chalk8.dim("Valid options: to-codex, from-codex, bidirectional"));
1019
1435
  process.exit(1);
1020
1436
  }
1021
1437
  const direction = options.direction;
@@ -1026,7 +1442,7 @@ function syncProjectCommand() {
1026
1442
  const syncManager = createSyncManager({
1027
1443
  localStorage,
1028
1444
  config: config.sync,
1029
- manifestPath: path3.join(process.cwd(), ".fractary", ".codex-sync-manifest.json")
1445
+ manifestPath: path4.join(process.cwd(), ".fractary", ".codex-sync-manifest.json")
1030
1446
  });
1031
1447
  const defaultPatterns = config.sync?.include || [
1032
1448
  "docs/**/*.md",
@@ -1079,7 +1495,7 @@ function syncProjectCommand() {
1079
1495
  synced: 0
1080
1496
  }, null, 2));
1081
1497
  } else {
1082
- console.log(chalk6.yellow("No files to sync."));
1498
+ console.log(chalk8.yellow("No files to sync."));
1083
1499
  }
1084
1500
  return;
1085
1501
  }
@@ -1122,364 +1538,90 @@ function syncProjectCommand() {
1122
1538
  }, null, 2));
1123
1539
  return;
1124
1540
  }
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))}`);
1541
+ console.log(chalk8.bold("Sync Plan\n"));
1542
+ console.log(` Project: ${chalk8.cyan(projectName)}`);
1543
+ console.log(` Organization: ${chalk8.cyan(config.organization)}`);
1544
+ console.log(` Environment: ${chalk8.cyan(options.env)} (${targetBranch})`);
1545
+ console.log(` Direction: ${chalk8.cyan(direction)}`);
1546
+ console.log(` Files: ${chalk8.cyan(plan.totalFiles.toString())}`);
1547
+ console.log(` Total size: ${chalk8.cyan(formatBytes(plan.totalBytes))}`);
1132
1548
  if (plan.estimatedTime) {
1133
- console.log(` Est. time: ${chalk6.dim(formatDuration(plan.estimatedTime))}`);
1549
+ console.log(` Est. time: ${chalk8.dim(formatDuration(plan.estimatedTime))}`);
1134
1550
  }
1135
1551
  console.log("");
1136
1552
  if (plan.conflicts.length > 0) {
1137
- console.log(chalk6.yellow(`\u26A0 ${plan.conflicts.length} conflicts detected:`));
1553
+ console.log(chalk8.yellow(`\u26A0 ${plan.conflicts.length} conflicts detected:`));
1138
1554
  for (const conflict of plan.conflicts.slice(0, 5)) {
1139
- console.log(chalk6.yellow(` \u2022 ${conflict.path}`));
1555
+ console.log(chalk8.yellow(` \u2022 ${conflict.path}`));
1140
1556
  }
1141
1557
  if (plan.conflicts.length > 5) {
1142
- console.log(chalk6.dim(` ... and ${plan.conflicts.length - 5} more`));
1558
+ console.log(chalk8.dim(` ... and ${plan.conflicts.length - 5} more`));
1143
1559
  }
1144
1560
  console.log("");
1145
1561
  }
1146
1562
  if (plan.skipped.length > 0) {
1147
- console.log(chalk6.dim(`${plan.skipped.length} files skipped (no changes)`));
1563
+ console.log(chalk8.dim(`${plan.skipped.length} files skipped (no changes)`));
1148
1564
  console.log("");
1149
1565
  }
1150
1566
  if (options.dryRun) {
1151
- console.log(chalk6.blue("Dry run - would sync:\n"));
1567
+ console.log(chalk8.blue("Dry run - would sync:\n"));
1152
1568
  const filesToShow = plan.files.slice(0, 10);
1153
1569
  for (const file of filesToShow) {
1154
1570
  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;
1571
+ const opColor = file.operation === "create" ? chalk8.green : file.operation === "update" ? chalk8.yellow : chalk8.dim;
1156
1572
  console.log(
1157
- chalk6.dim(` ${arrow}`),
1573
+ chalk8.dim(` ${arrow}`),
1158
1574
  opColor(file.operation.padEnd(7)),
1159
1575
  file.path,
1160
- chalk6.dim(`(${formatBytes(file.size || 0)})`)
1576
+ chalk8.dim(`(${formatBytes(file.size || 0)})`)
1161
1577
  );
1162
1578
  }
1163
1579
  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
- }
1580
+ console.log(chalk8.dim(` ... and ${plan.files.length - 10} more files`));
1431
1581
  }
1582
+ console.log(chalk8.dim(`
1583
+ Total: ${plan.totalFiles} files (${formatBytes(plan.totalBytes)})`));
1584
+ console.log(chalk8.dim("Run without --dry-run to execute sync."));
1585
+ return;
1432
1586
  }
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));
1587
+ console.log(chalk8.blue("Syncing...\n"));
1588
+ const startTime = Date.now();
1589
+ const result = await syncManager.executePlan(plan, syncOptions);
1590
+ const duration = Date.now() - startTime;
1591
+ console.log("");
1592
+ if (result.success) {
1593
+ console.log(chalk8.green(`\u2713 Sync completed successfully`));
1594
+ console.log(chalk8.dim(` Synced: ${result.synced} files`));
1595
+ if (result.skipped > 0) {
1596
+ console.log(chalk8.dim(` Skipped: ${result.skipped} files`));
1597
+ }
1598
+ console.log(chalk8.dim(` Duration: ${formatDuration(duration)}`));
1452
1599
  } 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)`));
1600
+ console.log(chalk8.yellow(`\u26A0 Sync completed with errors`));
1601
+ console.log(chalk8.green(` Synced: ${result.synced} files`));
1602
+ console.log(chalk8.red(` Failed: ${result.failed} files`));
1603
+ if (result.skipped > 0) {
1604
+ console.log(chalk8.dim(` Skipped: ${result.skipped} files`));
1457
1605
  }
1458
- if (failed.length > 0) {
1459
- console.log(chalk6.red(`\u2717 ${failed.length} repos failed`));
1606
+ if (result.errors.length > 0) {
1607
+ console.log("");
1608
+ console.log(chalk8.red("Errors:"));
1609
+ for (const error of result.errors.slice(0, 5)) {
1610
+ console.log(chalk8.red(` \u2022 ${error.path}: ${error.error}`));
1611
+ }
1612
+ if (result.errors.length > 5) {
1613
+ console.log(chalk8.dim(` ... and ${result.errors.length - 5} more errors`));
1614
+ }
1460
1615
  }
1461
1616
  }
1462
1617
  } 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
- }
1618
+ console.error(chalk8.red("Error:"), error.message);
1468
1619
  process.exit(1);
1469
1620
  }
1470
1621
  });
1471
1622
  return cmd;
1472
1623
  }
1473
1624
 
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
1625
  // src/commands/types/index.ts
1484
1626
  init_esm_shims();
1485
1627
 
@@ -1505,713 +1647,306 @@ function typesListCommand() {
1505
1647
  types = allTypes.filter((t) => registry.isBuiltIn(t.name));
1506
1648
  }
1507
1649
  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) {
1650
+ const builtinCount = types.filter((t) => registry.isBuiltIn(t.name)).length;
1651
+ const customCount = types.length - builtinCount;
1785
1652
  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."
1653
+ count: types.length,
1654
+ builtinCount,
1655
+ customCount,
1656
+ types: types.map((t) => ({
1657
+ name: t.name,
1658
+ description: t.description,
1659
+ patterns: t.patterns,
1660
+ defaultTtl: t.defaultTtl,
1661
+ ttl: formatTtl(t.defaultTtl),
1662
+ builtin: registry.isBuiltIn(t.name),
1663
+ archiveAfterDays: t.archiveAfterDays,
1664
+ archiveStorage: t.archiveStorage
1665
+ }))
1794
1666
  }, null, 2));
1795
1667
  return;
1796
1668
  }
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.'));
1669
+ if (types.length === 0) {
1670
+ console.log(chalk8.yellow("No types found."));
1671
+ return;
1672
+ }
1673
+ console.log(chalk8.bold("Artifact Types\n"));
1674
+ const builtinTypes = types.filter((t) => registry.isBuiltIn(t.name));
1675
+ const customTypes = types.filter((t) => !registry.isBuiltIn(t.name));
1676
+ if (builtinTypes.length > 0 && !options.customOnly) {
1677
+ console.log(chalk8.bold("Built-in Types"));
1678
+ console.log(chalk8.dim("\u2500".repeat(70)));
1679
+ for (const type of builtinTypes) {
1680
+ const patternStr = type.patterns[0] || "";
1681
+ console.log(` ${chalk8.cyan(type.name.padEnd(12))} ${patternStr.padEnd(30)} ${chalk8.dim(`TTL: ${formatTtl(type.defaultTtl)}`)}`);
1682
+ console.log(` ${chalk8.dim(" ".repeat(12) + type.description)}`);
1683
+ }
1684
+ console.log("");
1685
+ }
1686
+ if (customTypes.length > 0 && !options.builtinOnly) {
1687
+ console.log(chalk8.bold("Custom Types"));
1688
+ console.log(chalk8.dim("\u2500".repeat(70)));
1689
+ for (const type of customTypes) {
1690
+ const patternStr = type.patterns[0] || "";
1691
+ console.log(` ${chalk8.green(type.name.padEnd(12))} ${patternStr.padEnd(30)} ${chalk8.dim(`TTL: ${formatTtl(type.defaultTtl)}`)}`);
1692
+ console.log(` ${chalk8.dim(" ".repeat(12) + type.description)}`);
1693
+ }
1694
+ console.log("");
1808
1695
  }
1696
+ console.log(chalk8.dim(`Total: ${types.length} types (${builtinTypes.length} built-in, ${customTypes.length} custom)`));
1697
+ } catch (error) {
1698
+ console.error(chalk8.red("Error:"), error.message);
1809
1699
  process.exit(1);
1810
1700
  }
1811
1701
  });
1812
1702
  return cmd;
1813
1703
  }
1814
1704
 
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
1705
+ // src/commands/types/show.ts
1827
1706
  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`;
1707
+ function formatTtl2(seconds) {
1708
+ if (seconds < 60) return `${seconds}s`;
1709
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
1710
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`;
1711
+ return `${Math.floor(seconds / 86400)}d`;
2009
1712
  }
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) => {
1713
+ function typesShowCommand() {
1714
+ const cmd = new Command("show");
1715
+ cmd.description("Show details for a specific type").argument("<name>", "Type name").option("--json", "Output as JSON").action(async (name, options) => {
2013
1716
  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;
1717
+ const client = await getClient();
1718
+ const registry = client.getTypeRegistry();
1719
+ const type = registry.get(name);
1720
+ if (!type) {
1721
+ console.error(chalk8.red("Error:"), `Type "${name}" not found.`);
1722
+ console.log(chalk8.dim('Run "fractary codex types list" to see available types.'));
1723
+ process.exit(1);
1724
+ }
1725
+ const isBuiltin = registry.isBuiltIn(name);
2023
1726
  if (options.json) {
2024
1727
  console.log(JSON.stringify({
2025
- summary: {
2026
- total: checks.length,
2027
- passed,
2028
- warned,
2029
- failed,
2030
- healthy: failed === 0
2031
- },
2032
- checks
1728
+ name: type.name,
1729
+ builtin: isBuiltin,
1730
+ description: type.description,
1731
+ patterns: type.patterns,
1732
+ defaultTtl: type.defaultTtl,
1733
+ ttl: formatTtl2(type.defaultTtl),
1734
+ archiveAfterDays: type.archiveAfterDays,
1735
+ archiveStorage: type.archiveStorage,
1736
+ syncPatterns: type.syncPatterns,
1737
+ excludePatterns: type.excludePatterns
2033
1738
  }, null, 2));
2034
1739
  return;
2035
1740
  }
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
- }
1741
+ const nameColor = isBuiltin ? chalk8.cyan : chalk8.green;
1742
+ console.log(chalk8.bold(`Type: ${nameColor(name)}
1743
+ `));
1744
+ console.log(` ${chalk8.dim("Source:")} ${isBuiltin ? "Built-in" : "Custom"}`);
1745
+ console.log(` ${chalk8.dim("Description:")} ${type.description}`);
1746
+ console.log(` ${chalk8.dim("TTL:")} ${formatTtl2(type.defaultTtl)} (${type.defaultTtl} seconds)`);
1747
+ console.log("");
1748
+ console.log(chalk8.bold("Patterns"));
1749
+ for (const pattern of type.patterns) {
1750
+ console.log(` ${chalk8.dim("\u2022")} ${pattern}`);
1751
+ }
1752
+ if (type.archiveAfterDays !== null) {
2045
1753
  console.log("");
1754
+ console.log(chalk8.bold("Archive Settings"));
1755
+ console.log(` ${chalk8.dim("After:")} ${type.archiveAfterDays} days`);
1756
+ console.log(` ${chalk8.dim("Storage:")} ${type.archiveStorage || "not set"}`);
2046
1757
  }
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) {
1758
+ if (type.syncPatterns && type.syncPatterns.length > 0) {
2052
1759
  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"));
1760
+ console.log(chalk8.bold("Sync Patterns"));
1761
+ for (const pattern of type.syncPatterns) {
1762
+ console.log(` ${chalk8.dim("\u2022")} ${pattern}`);
1763
+ }
2056
1764
  }
2057
- if (failed > 0) {
2058
- process.exit(1);
1765
+ if (type.excludePatterns && type.excludePatterns.length > 0) {
1766
+ console.log("");
1767
+ console.log(chalk8.bold("Exclude Patterns"));
1768
+ for (const pattern of type.excludePatterns) {
1769
+ console.log(` ${chalk8.dim("\u2022")} ${pattern}`);
1770
+ }
2059
1771
  }
2060
1772
  } catch (error) {
2061
- console.error(chalk6.red("Error:"), error.message);
1773
+ console.error(chalk8.red("Error:"), error.message);
2062
1774
  process.exit(1);
2063
1775
  }
2064
1776
  });
2065
1777
  return cmd;
2066
1778
  }
2067
1779
 
2068
- // src/commands/migrate.ts
1780
+ // src/commands/types/add.ts
2069
1781
  init_esm_shims();
2070
1782
  init_migrate_config();
2071
- async function fileExists3(filePath) {
2072
- try {
2073
- await fs.access(filePath);
2074
- return true;
2075
- } catch {
2076
- return false;
1783
+ function isValidTypeName(name) {
1784
+ return /^[a-z][a-z0-9-]*$/.test(name);
1785
+ }
1786
+ function parseTtl(ttl) {
1787
+ const match = ttl.match(/^(\d+)([smhd])$/);
1788
+ if (!match) {
1789
+ throw new Error("Invalid TTL format");
1790
+ }
1791
+ const value = parseInt(match[1], 10);
1792
+ const unit = match[2];
1793
+ switch (unit) {
1794
+ case "s":
1795
+ return value;
1796
+ case "m":
1797
+ return value * 60;
1798
+ case "h":
1799
+ return value * 3600;
1800
+ case "d":
1801
+ return value * 86400;
1802
+ default:
1803
+ throw new Error("Unknown TTL unit");
2077
1804
  }
2078
1805
  }
2079
- async function readFileContent(filePath) {
2080
- return fs.readFile(filePath, "utf-8");
1806
+ function formatTtl3(seconds) {
1807
+ if (seconds < 60) return `${seconds}s`;
1808
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
1809
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`;
1810
+ return `${Math.floor(seconds / 86400)}d`;
2081
1811
  }
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) => {
1812
+ function typesAddCommand() {
1813
+ const cmd = new Command("add");
1814
+ 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) => {
2085
1815
  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;
1816
+ if (!isValidTypeName(name)) {
1817
+ console.error(chalk8.red("Error:"), "Invalid type name.");
1818
+ console.log(chalk8.dim("Type name must be lowercase, start with a letter, and contain only letters, numbers, and hyphens."));
1819
+ process.exit(1);
2101
1820
  }
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;
1821
+ const client = await getClient();
1822
+ const registry = client.getTypeRegistry();
1823
+ if (registry.isBuiltIn(name)) {
1824
+ console.error(chalk8.red("Error:"), `Cannot override built-in type "${name}".`);
1825
+ const builtinNames = registry.list().filter((t) => registry.isBuiltIn(t.name)).map((t) => t.name);
1826
+ console.log(chalk8.dim("Built-in types: " + builtinNames.join(", ")));
1827
+ process.exit(1);
2115
1828
  }
2116
- const legacyContent = await readFileContent(legacyConfigPath);
2117
- let legacyConfig;
1829
+ if (registry.has(name)) {
1830
+ console.error(chalk8.red("Error:"), `Custom type "${name}" already exists.`);
1831
+ console.log(chalk8.dim('Use "fractary codex types remove" first to remove it.'));
1832
+ process.exit(1);
1833
+ }
1834
+ let ttlSeconds;
2118
1835
  try {
2119
- legacyConfig = JSON.parse(legacyContent);
1836
+ ttlSeconds = parseTtl(options.ttl);
2120
1837
  } catch {
2121
- console.error(chalk6.red("Error:"), "Invalid JSON in legacy config file.");
1838
+ console.error(chalk8.red("Error:"), "Invalid TTL format.");
1839
+ console.log(chalk8.dim("Expected format: <number><unit> where unit is s (seconds), m (minutes), h (hours), or d (days)"));
1840
+ console.log(chalk8.dim("Examples: 30m, 24h, 7d"));
2122
1841
  process.exit(1);
2123
1842
  }
2124
- if (!options.json && !options.dryRun) {
2125
- console.log(chalk6.blue("Migrating Codex configuration to v3.0 YAML format...\n"));
1843
+ const configPath = path4.join(process.cwd(), ".fractary", "codex.yaml");
1844
+ const config = await readYamlConfig(configPath);
1845
+ if (!config.types) {
1846
+ config.types = { custom: {} };
2126
1847
  }
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
- }
1848
+ if (!config.types.custom) {
1849
+ config.types.custom = {};
2159
1850
  }
1851
+ config.types.custom[name] = {
1852
+ description: options.description || `Custom type: ${name}`,
1853
+ patterns: [options.pattern],
1854
+ defaultTtl: ttlSeconds
1855
+ };
1856
+ await writeYamlConfig(config, configPath);
2160
1857
  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
1858
+ console.log(JSON.stringify({
1859
+ success: true,
1860
+ type: {
1861
+ name,
1862
+ description: config.types.custom[name].description,
1863
+ patterns: config.types.custom[name].patterns,
1864
+ defaultTtl: ttlSeconds,
1865
+ ttl: formatTtl3(ttlSeconds),
1866
+ builtin: false
2171
1867
  },
2172
- warnings: migrationResult.warnings,
2173
- backupPath: migrationResult.backupPath
2174
- };
2175
- console.log(JSON.stringify(output, null, 2));
2176
- if (options.dryRun) {
2177
- return;
2178
- }
1868
+ message: "Custom type added successfully. Changes will take effect on next CLI invocation."
1869
+ }, null, 2));
1870
+ return;
2179
1871
  }
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
- }
1872
+ console.log(chalk8.green("\u2713"), `Added custom type "${chalk8.cyan(name)}"`);
1873
+ console.log("");
1874
+ console.log(` ${chalk8.dim("Pattern:")} ${options.pattern}`);
1875
+ console.log(` ${chalk8.dim("TTL:")} ${formatTtl3(ttlSeconds)} (${ttlSeconds} seconds)`);
1876
+ if (options.description) {
1877
+ console.log(` ${chalk8.dim("Description:")} ${options.description}`);
2206
1878
  }
1879
+ console.log("");
1880
+ console.log(chalk8.dim("Note: Custom type will be available on next CLI invocation."));
2207
1881
  } catch (error) {
1882
+ console.error(chalk8.red("Error:"), error.message);
1883
+ if (error.message.includes("Failed to load configuration")) {
1884
+ console.log(chalk8.dim('\nRun "fractary codex init" to create a configuration.'));
1885
+ }
1886
+ process.exit(1);
1887
+ }
1888
+ });
1889
+ return cmd;
1890
+ }
1891
+
1892
+ // src/commands/types/remove.ts
1893
+ init_esm_shims();
1894
+ init_migrate_config();
1895
+ function typesRemoveCommand() {
1896
+ const cmd = new Command("remove");
1897
+ 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) => {
1898
+ try {
1899
+ const client = await getClient();
1900
+ const registry = client.getTypeRegistry();
1901
+ if (registry.isBuiltIn(name)) {
1902
+ console.error(chalk8.red("Error:"), `Cannot remove built-in type "${name}".`);
1903
+ console.log(chalk8.dim("Built-in types are permanent and cannot be removed."));
1904
+ process.exit(1);
1905
+ }
1906
+ if (!registry.has(name)) {
1907
+ console.error(chalk8.red("Error:"), `Custom type "${name}" not found.`);
1908
+ console.log(chalk8.dim('Run "fractary codex types list --custom-only" to see custom types.'));
1909
+ process.exit(1);
1910
+ }
1911
+ const typeInfo = registry.get(name);
1912
+ const configPath = path4.join(process.cwd(), ".fractary", "codex.yaml");
1913
+ const config = await readYamlConfig(configPath);
1914
+ if (!config.types?.custom?.[name]) {
1915
+ console.error(chalk8.red("Error:"), `Custom type "${name}" not found in configuration.`);
1916
+ process.exit(1);
1917
+ }
1918
+ delete config.types.custom[name];
1919
+ if (Object.keys(config.types.custom).length === 0) {
1920
+ delete config.types.custom;
1921
+ }
1922
+ if (config.types && Object.keys(config.types).length === 0) {
1923
+ delete config.types;
1924
+ }
1925
+ await writeYamlConfig(config, configPath);
2208
1926
  if (options.json) {
2209
1927
  console.log(JSON.stringify({
2210
- status: "error",
2211
- message: error.message
2212
- }));
2213
- } else {
2214
- console.error(chalk6.red("Error:"), error.message);
1928
+ success: true,
1929
+ removed: {
1930
+ name: typeInfo.name,
1931
+ description: typeInfo.description,
1932
+ patterns: typeInfo.patterns,
1933
+ defaultTtl: typeInfo.defaultTtl
1934
+ },
1935
+ message: "Custom type removed successfully. Changes will take effect on next CLI invocation."
1936
+ }, null, 2));
1937
+ return;
1938
+ }
1939
+ console.log(chalk8.green("\u2713"), `Removed custom type "${chalk8.cyan(name)}"`);
1940
+ console.log("");
1941
+ console.log(chalk8.dim("Removed configuration:"));
1942
+ console.log(` ${chalk8.dim("Pattern:")} ${typeInfo.patterns.join(", ")}`);
1943
+ console.log(` ${chalk8.dim("Description:")} ${typeInfo.description}`);
1944
+ console.log("");
1945
+ console.log(chalk8.dim("Note: Custom type will be removed on next CLI invocation."));
1946
+ } catch (error) {
1947
+ console.error(chalk8.red("Error:"), error.message);
1948
+ if (error.message.includes("Failed to load configuration")) {
1949
+ console.log(chalk8.dim('\nRun "fractary codex init" to create a configuration.'));
2215
1950
  }
2216
1951
  process.exit(1);
2217
1952
  }
@@ -2219,17 +1954,26 @@ function migrateCommand() {
2219
1954
  return cmd;
2220
1955
  }
2221
1956
 
1957
+ // src/commands/types/index.ts
1958
+ function typesCommand() {
1959
+ const cmd = new Command("types");
1960
+ cmd.description("Manage artifact type registry");
1961
+ cmd.addCommand(typesListCommand());
1962
+ cmd.addCommand(typesShowCommand());
1963
+ cmd.addCommand(typesAddCommand());
1964
+ cmd.addCommand(typesRemoveCommand());
1965
+ return cmd;
1966
+ }
1967
+
2222
1968
  // src/cli.ts
2223
1969
  function createCLI() {
2224
1970
  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());
1971
+ program.description("Centralized knowledge management and distribution for AI agents").version("0.3.0");
1972
+ program.addCommand(documentCommand());
1973
+ program.addCommand(configCommand());
2228
1974
  program.addCommand(cacheCommand());
2229
1975
  program.addCommand(syncCommand());
2230
1976
  program.addCommand(typesCommand());
2231
- program.addCommand(healthCommand());
2232
- program.addCommand(migrateCommand());
2233
1977
  return program;
2234
1978
  }
2235
1979
  async function main() {