@fractary/codex-cli 0.10.11 → 0.10.14

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
@@ -3,8 +3,8 @@ import * as path5 from 'path';
3
3
  import { dirname, join } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  import * as fs from 'fs/promises';
6
- import * as yaml from 'js-yaml';
7
- import { ValidationError, PermissionDeniedError, ConfigurationError, CodexError } from '@fractary/codex';
6
+ import * as yaml2 from 'js-yaml';
7
+ import { readCodexConfig, CONFIG_SCHEMA_VERSION, expandEnvVarsInConfig, expandEnvVars, parseSize, parseDuration, ValidationError, PermissionDeniedError, ConfigurationError, CodexError } from '@fractary/codex';
8
8
  import * as os from 'os';
9
9
  import { spawn } from 'child_process';
10
10
  import { Command } from 'commander';
@@ -38,7 +38,7 @@ __export(migrate_config_exports, {
38
38
  getDefaultYamlConfig: () => getDefaultYamlConfig,
39
39
  isLegacyConfig: () => isLegacyConfig,
40
40
  migrateConfig: () => migrateConfig,
41
- readYamlConfig: () => readYamlConfig,
41
+ readYamlConfig: () => readCodexConfig,
42
42
  writeYamlConfig: () => writeYamlConfig
43
43
  });
44
44
  async function isLegacyConfig(configPath) {
@@ -163,7 +163,7 @@ async function migrateConfig(legacyConfigPath, options) {
163
163
  async function writeYamlConfig(config, outputPath) {
164
164
  const dir = path5.dirname(outputPath);
165
165
  await fs.mkdir(dir, { recursive: true });
166
- const yamlContent = yaml.dump(config, {
166
+ const yamlContent = yaml2.dump(config, {
167
167
  indent: 2,
168
168
  lineWidth: 80,
169
169
  noRefs: true,
@@ -228,20 +228,6 @@ function getDefaultYamlConfig(organization) {
228
228
  }
229
229
  };
230
230
  }
231
- async function readYamlConfig(configPath) {
232
- const content = await fs.readFile(configPath, "utf-8");
233
- const rawConfig = yaml.load(content);
234
- let config;
235
- if (rawConfig.codex && typeof rawConfig.codex === "object") {
236
- config = rawConfig.codex;
237
- } else {
238
- config = rawConfig;
239
- }
240
- if (!config.organization) {
241
- throw new Error("Invalid config: organization is required");
242
- }
243
- return config;
244
- }
245
231
  var init_migrate_config = __esm({
246
232
  "src/config/migrate-config.ts"() {
247
233
  init_esm_shims();
@@ -253,89 +239,9 @@ var config_types_exports = {};
253
239
  __export(config_types_exports, {
254
240
  parseDuration: () => parseDuration,
255
241
  parseSize: () => parseSize,
256
- resolveEnvVars: () => resolveEnvVars,
257
- resolveEnvVarsInConfig: () => resolveEnvVarsInConfig
242
+ resolveEnvVars: () => expandEnvVars,
243
+ resolveEnvVarsInConfig: () => expandEnvVarsInConfig
258
244
  });
259
- function parseDuration(duration) {
260
- if (typeof duration === "number") {
261
- return duration;
262
- }
263
- const match = duration.match(/^(\d+)([smhdwMy])$/);
264
- if (!match) {
265
- throw new Error(`Invalid duration format: ${duration}`);
266
- }
267
- const [, valueStr, unit] = match;
268
- const value = parseInt(valueStr, 10);
269
- switch (unit) {
270
- case "s":
271
- return value;
272
- case "m":
273
- return value * 60;
274
- case "h":
275
- return value * 3600;
276
- case "d":
277
- return value * 86400;
278
- case "w":
279
- return value * 604800;
280
- case "M":
281
- return value * 2592e3;
282
- // 30 days
283
- case "y":
284
- return value * 31536e3;
285
- // 365 days
286
- default:
287
- throw new Error(`Unknown duration unit: ${unit}`);
288
- }
289
- }
290
- function parseSize(size) {
291
- if (typeof size === "number") {
292
- return size;
293
- }
294
- const match = size.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB)$/i);
295
- if (!match) {
296
- throw new Error(`Invalid size format: ${size}`);
297
- }
298
- const [, valueStr, unit] = match;
299
- const value = parseFloat(valueStr);
300
- switch (unit.toUpperCase()) {
301
- case "B":
302
- return value;
303
- case "KB":
304
- return value * 1024;
305
- case "MB":
306
- return value * 1024 * 1024;
307
- case "GB":
308
- return value * 1024 * 1024 * 1024;
309
- default:
310
- throw new Error(`Unknown size unit: ${unit}`);
311
- }
312
- }
313
- function resolveEnvVars(value) {
314
- return value.replace(/\$\{([^}]+)\}/g, (_, varName) => {
315
- const envValue = process.env[varName];
316
- if (envValue === void 0) {
317
- console.warn(`Warning: Environment variable ${varName} is not set`);
318
- return `\${${varName}}`;
319
- }
320
- return envValue;
321
- });
322
- }
323
- function resolveEnvVarsInConfig(config) {
324
- if (typeof config === "string") {
325
- return resolveEnvVars(config);
326
- }
327
- if (Array.isArray(config)) {
328
- return config.map((item) => resolveEnvVarsInConfig(item));
329
- }
330
- if (config !== null && typeof config === "object") {
331
- const result = {};
332
- for (const [key, value] of Object.entries(config)) {
333
- result[key] = resolveEnvVarsInConfig(value);
334
- }
335
- return result;
336
- }
337
- return config;
338
- }
339
245
  var init_config_types = __esm({
340
246
  "src/config/config-types.ts"() {
341
247
  init_esm_shims();
@@ -388,14 +294,14 @@ var init_codex_client = __esm({
388
294
  CodexError: CodexError2,
389
295
  ConfigurationError: ConfigurationError2
390
296
  } = await import('@fractary/codex');
391
- const { readYamlConfig: readYamlConfig2 } = await Promise.resolve().then(() => (init_migrate_config(), migrate_config_exports));
392
- const { resolveEnvVarsInConfig: resolveEnvVarsInConfig2 } = await Promise.resolve().then(() => (init_config_types(), config_types_exports));
297
+ const { readYamlConfig } = await Promise.resolve().then(() => (init_migrate_config(), migrate_config_exports));
298
+ const { resolveEnvVarsInConfig } = await Promise.resolve().then(() => (init_config_types(), config_types_exports));
393
299
  try {
394
300
  const configPath = path5.join(process.cwd(), ".fractary", "config.yaml");
395
301
  let config;
396
302
  try {
397
- config = await readYamlConfig2(configPath);
398
- config = resolveEnvVarsInConfig2(config);
303
+ config = await readYamlConfig(configPath);
304
+ config = resolveEnvVarsInConfig(config);
399
305
  } catch (error) {
400
306
  throw new ConfigurationError2(
401
307
  `Failed to load configuration from ${configPath}. Run "fractary codex init" to create a configuration.`
@@ -884,7 +790,7 @@ function getDefaultUnifiedConfig(organization, project, codexRepo) {
884
790
  const sanitizedProject = sanitizeForS3BucketName(project);
885
791
  return {
886
792
  file: {
887
- schema_version: "2.0",
793
+ schema_version: CONFIG_SCHEMA_VERSION,
888
794
  sources: {
889
795
  specs: {
890
796
  type: "s3",
@@ -921,18 +827,23 @@ function getDefaultUnifiedConfig(organization, project, codexRepo) {
921
827
  }
922
828
  },
923
829
  codex: {
924
- schema_version: "2.0",
830
+ schema_version: CONFIG_SCHEMA_VERSION,
925
831
  organization,
926
832
  project,
927
833
  codex_repo: codexRepo,
928
- dependencies: {}
834
+ remotes: {
835
+ // The codex repository - uses same token as git operations
836
+ [`${organization}/${codexRepo}`]: {
837
+ token: "${GITHUB_TOKEN}"
838
+ }
839
+ }
929
840
  }
930
841
  };
931
842
  }
932
843
  async function readUnifiedConfig(configPath) {
933
844
  try {
934
845
  const content = await fs.readFile(configPath, "utf-8");
935
- const config = yaml.load(content);
846
+ const config = yaml2.load(content);
936
847
  return config;
937
848
  } catch (error) {
938
849
  if (error.code === "ENOENT") {
@@ -944,7 +855,7 @@ async function readUnifiedConfig(configPath) {
944
855
  async function writeUnifiedConfig(config, outputPath) {
945
856
  const dir = path5.dirname(outputPath);
946
857
  await fs.mkdir(dir, { recursive: true });
947
- const yamlContent = yaml.dump(config, {
858
+ const yamlContent = yaml2.dump(config, {
948
859
  indent: 2,
949
860
  lineWidth: 120,
950
861
  noRefs: true,
@@ -956,7 +867,7 @@ function mergeUnifiedConfigs(existing, updates) {
956
867
  const merged = {};
957
868
  if (updates.file || existing.file) {
958
869
  merged.file = {
959
- schema_version: updates.file?.schema_version || existing.file?.schema_version || "2.0",
870
+ schema_version: updates.file?.schema_version || existing.file?.schema_version || CONFIG_SCHEMA_VERSION,
960
871
  sources: {
961
872
  ...existing.file?.sources || {},
962
873
  ...updates.file?.sources || {}
@@ -965,13 +876,13 @@ function mergeUnifiedConfigs(existing, updates) {
965
876
  }
966
877
  if (updates.codex || existing.codex) {
967
878
  merged.codex = {
968
- schema_version: updates.codex?.schema_version || existing.codex?.schema_version || "2.0",
879
+ schema_version: updates.codex?.schema_version || existing.codex?.schema_version || CONFIG_SCHEMA_VERSION,
969
880
  organization: updates.codex?.organization || existing.codex?.organization || "default",
970
881
  project: updates.codex?.project || existing.codex?.project || "default",
971
882
  codex_repo: updates.codex?.codex_repo || existing.codex?.codex_repo || "",
972
- dependencies: {
973
- ...existing.codex?.dependencies || {},
974
- ...updates.codex?.dependencies || {}
883
+ remotes: {
884
+ ...existing.codex?.remotes || {},
885
+ ...updates.codex?.remotes || {}
975
886
  }
976
887
  };
977
888
  }
@@ -1185,9 +1096,64 @@ async function fileExists(filePath) {
1185
1096
  return false;
1186
1097
  }
1187
1098
  }
1099
+ async function installMcpServer(projectRoot, configPath = ".fractary/config.yaml", options = {}) {
1100
+ const mcpJsonPath = path5.join(projectRoot, ".mcp.json");
1101
+ const { backup = true } = options;
1102
+ let existingConfig = { mcpServers: {} };
1103
+ let backupPath;
1104
+ let migrated = false;
1105
+ if (await fileExists(mcpJsonPath)) {
1106
+ try {
1107
+ const content = await fs.readFile(mcpJsonPath, "utf-8");
1108
+ existingConfig = JSON.parse(content);
1109
+ if (backup) {
1110
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").slice(0, 18);
1111
+ const suffix = Math.random().toString(36).substring(2, 6);
1112
+ backupPath = `${mcpJsonPath}.backup.${timestamp}-${suffix}`;
1113
+ await fs.writeFile(backupPath, content);
1114
+ }
1115
+ } catch {
1116
+ console.log(chalk7.yellow("\u26A0 Warning: .mcp.json contains invalid JSON, starting fresh"));
1117
+ existingConfig = { mcpServers: {} };
1118
+ }
1119
+ }
1120
+ if (!existingConfig.mcpServers) {
1121
+ existingConfig.mcpServers = {};
1122
+ }
1123
+ const existing = existingConfig.mcpServers["fractary-codex"];
1124
+ if (existing) {
1125
+ const existingCommand = existing.command;
1126
+ const existingArgs = existing.args || [];
1127
+ if (existingCommand === "npx" && existingArgs.includes("@fractary/codex-mcp")) {
1128
+ return {
1129
+ installed: false,
1130
+ migrated: false,
1131
+ alreadyInstalled: true,
1132
+ backupPath
1133
+ };
1134
+ }
1135
+ if (existingCommand === "node" || existingArgs.includes("@fractary/codex")) {
1136
+ migrated = true;
1137
+ }
1138
+ }
1139
+ existingConfig.mcpServers["fractary-codex"] = {
1140
+ command: "npx",
1141
+ args: ["-y", "@fractary/codex-mcp", "--config", configPath]
1142
+ };
1143
+ await fs.writeFile(
1144
+ mcpJsonPath,
1145
+ JSON.stringify(existingConfig, null, 2) + "\n"
1146
+ );
1147
+ return {
1148
+ installed: true,
1149
+ migrated,
1150
+ alreadyInstalled: false,
1151
+ backupPath
1152
+ };
1153
+ }
1188
1154
  function initCommand() {
1189
1155
  const cmd = new Command("init");
1190
- cmd.description("Initialize unified Fractary configuration (.fractary/config.yaml)").option("--org <slug>", 'Organization slug (e.g., "fractary")').option("--project <name>", "Project name (default: derived from directory)").option("--codex-repo <name>", 'Codex repository name (e.g., "codex.fractary.com")').option("--force", "Overwrite existing configuration").action(async (options) => {
1156
+ cmd.description("Initialize unified Fractary configuration (.fractary/config.yaml)").option("--org <slug>", 'Organization slug (e.g., "fractary")').option("--project <name>", "Project name (default: derived from directory)").option("--codex-repo <name>", 'Codex repository name (e.g., "codex.fractary.com")').option("--force", "Overwrite existing configuration").option("--no-mcp", "Skip MCP server installation").action(async (options) => {
1191
1157
  try {
1192
1158
  console.log(chalk7.blue("Initializing unified Fractary configuration...\n"));
1193
1159
  let org = options.org;
@@ -1299,6 +1265,20 @@ function initCommand() {
1299
1265
  } else if (result.merged) {
1300
1266
  console.log(chalk7.green("\u2713"), chalk7.dim(".fractary/config.yaml (merged with existing)"));
1301
1267
  }
1268
+ if (options.mcp !== false) {
1269
+ console.log("\nConfiguring MCP server...");
1270
+ const mcpResult = await installMcpServer(process.cwd(), ".fractary/config.yaml");
1271
+ if (mcpResult.alreadyInstalled) {
1272
+ console.log(chalk7.green("\u2713"), chalk7.dim(".mcp.json (already configured)"));
1273
+ } else if (mcpResult.migrated) {
1274
+ console.log(chalk7.green("\u2713"), chalk7.dim(".mcp.json (migrated from old format)"));
1275
+ if (mcpResult.backupPath) {
1276
+ console.log(chalk7.dim(` Backup: ${path5.basename(mcpResult.backupPath)}`));
1277
+ }
1278
+ } else if (mcpResult.installed) {
1279
+ console.log(chalk7.green("\u2713"), chalk7.dim(".mcp.json (created)"));
1280
+ }
1281
+ }
1302
1282
  console.log(chalk7.green("\n\u2713 Unified configuration initialized successfully!\n"));
1303
1283
  console.log(chalk7.bold("Configuration:"));
1304
1284
  console.log(chalk7.dim(` Organization: ${org}`));
@@ -1310,18 +1290,19 @@ function initCommand() {
1310
1290
  console.log(chalk7.dim(" - logs: .fractary/logs/ \u2192 S3"));
1311
1291
  console.log(chalk7.bold("\nCodex plugin:"));
1312
1292
  console.log(chalk7.dim(" - Cache: .fractary/codex/cache/"));
1313
- console.log(chalk7.dim(" - Dependencies: (none configured)"));
1293
+ console.log(chalk7.dim(" - MCP Server: @fractary/codex-mcp (via npx)"));
1294
+ console.log(chalk7.dim(" - Remotes: codex repo configured"));
1314
1295
  console.log(chalk7.bold("\nGit Authentication:"));
1315
1296
  console.log(chalk7.dim(" Codex sync uses your existing git credentials."));
1316
1297
  console.log(chalk7.dim(" Ensure you have access to the codex repository:"));
1317
1298
  console.log(chalk7.dim(` gh repo view ${org}/${codexRepo}`));
1318
1299
  console.log(chalk7.dim(" Or set GITHUB_TOKEN environment variable."));
1319
1300
  console.log(chalk7.bold("\nNext steps:"));
1320
- console.log(chalk7.dim(" 1. Verify codex repository access: gh repo view " + org + "/" + codexRepo));
1321
- console.log(chalk7.dim(" 2. Configure AWS credentials for S3 access (if using file plugin)"));
1322
- console.log(chalk7.dim(" 3. Edit .fractary/config.yaml to add external project dependencies"));
1323
- console.log(chalk7.dim(" 4. Access current project files: codex://specs/SPEC-001.md"));
1324
- console.log(chalk7.dim(" 5. Access external projects: codex://org/project/docs/README.md"));
1301
+ console.log(chalk7.dim(" 1. Restart Claude Code to load the MCP server"));
1302
+ console.log(chalk7.dim(" 2. Verify codex repository access: gh repo view " + org + "/" + codexRepo));
1303
+ console.log(chalk7.dim(" 3. Configure AWS credentials for S3 access (if using file plugin)"));
1304
+ console.log(chalk7.dim(" 4. Edit .fractary/config.yaml to add external project remotes"));
1305
+ console.log(chalk7.dim(" 5. Reference docs via codex:// URIs (auto-fetched by MCP)"));
1325
1306
  } catch (error) {
1326
1307
  console.error(chalk7.red("Error:"), error.message);
1327
1308
  process.exit(1);
@@ -1539,7 +1520,7 @@ async function checkConfiguration() {
1539
1520
  details: 'Run "fractary codex init" to create configuration'
1540
1521
  };
1541
1522
  }
1542
- const config = await readYamlConfig(configPath);
1523
+ const config = await readCodexConfig(configPath);
1543
1524
  if (!config.organization) {
1544
1525
  return {
1545
1526
  name: "Configuration",
@@ -1630,7 +1611,7 @@ async function checkCache() {
1630
1611
  async function checkStorage() {
1631
1612
  const configPath = path5.join(process.cwd(), ".fractary", "config.yaml");
1632
1613
  try {
1633
- const config = await readYamlConfig(configPath);
1614
+ const config = await readCodexConfig(configPath);
1634
1615
  const providers = config.storage || [];
1635
1616
  if (providers.length === 0) {
1636
1617
  return {
@@ -1789,7 +1770,7 @@ function syncCommand() {
1789
1770
  const configPath = path5.join(process.cwd(), ".fractary", "config.yaml");
1790
1771
  let config;
1791
1772
  try {
1792
- config = await readYamlConfig(configPath);
1773
+ config = await readCodexConfig(configPath);
1793
1774
  } catch (error) {
1794
1775
  console.error(chalk7.red("Error:"), "Codex not initialized.");
1795
1776
  console.log(chalk7.dim('Run "fractary codex init" first.'));
@@ -2357,7 +2338,7 @@ function typesAddCommand() {
2357
2338
  process.exit(1);
2358
2339
  }
2359
2340
  const configPath = path5.join(process.cwd(), ".fractary", "config.yaml");
2360
- const config = await readYamlConfig(configPath);
2341
+ const config = await readCodexConfig(configPath);
2361
2342
  if (!config.types) {
2362
2343
  config.types = { custom: {} };
2363
2344
  }
@@ -2426,7 +2407,7 @@ function typesRemoveCommand() {
2426
2407
  }
2427
2408
  const typeInfo = registry.get(name);
2428
2409
  const configPath = path5.join(process.cwd(), ".fractary", "config.yaml");
2429
- const config = await readYamlConfig(configPath);
2410
+ const config = await readCodexConfig(configPath);
2430
2411
  if (!config.types?.custom?.[name]) {
2431
2412
  console.error(chalk7.red("Error:"), `Custom type "${name}" not found in configuration.`);
2432
2413
  process.exit(1);