@fractary/faber-cli 1.4.4 → 1.5.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6BpC;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAehD;AAqkBD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAM3C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+BpC;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAehD;AAojBD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAM3C"}
@@ -7,6 +7,7 @@ import chalk from 'chalk';
7
7
  import fs from 'fs/promises';
8
8
  import { generateAppManifest, generateManifestHtml, exchangeCodeForCredentials, validateAppCredentials, getInstallationId, savePrivateKey, } from '../../lib/github-app-setup.js';
9
9
  import { parseCodeFromUrl, validateManifestCode, detectGitHubContext, isGitRepository, } from '../../utils/github-manifest.js';
10
+ import { loadYamlConfig, writeYamlConfig, getConfigPath } from '../../lib/yaml-config.js';
10
11
  /**
11
12
  * Create the auth setup command
12
13
  */
@@ -15,7 +16,7 @@ export function createAuthSetupCommand() {
15
16
  .description('Set up GitHub App authentication for FABER CLI')
16
17
  .option('--org <name>', 'GitHub organization name')
17
18
  .option('--repo <name>', 'GitHub repository name')
18
- .option('--config-path <path>', 'Path to config file', '.fractary/settings.json')
19
+ .option('--config-path <path>', 'Path to config file', '.fractary/config.yaml')
19
20
  .option('--show-manifest', 'Display manifest JSON before setup')
20
21
  .option('--no-save', 'Display credentials without saving')
21
22
  .action(async (options) => {
@@ -70,23 +71,24 @@ async function manualAppConfiguration(configPath, org, repo) {
70
71
  console.error(chalk.red(`\n❌ Private key file not found: ${expandedKeyPath}\n`));
71
72
  process.exit(1);
72
73
  }
73
- // Create config
74
- const config = {
75
- github: {
76
- organization: org,
77
- project: repo,
78
- app: {
79
- id: appId.toString(),
80
- installation_id: installationId.toString(),
81
- private_key_path: privateKeyPath,
82
- },
83
- },
74
+ // Load existing config or create new one
75
+ let config = loadYamlConfig() || {
76
+ version: '2.0',
84
77
  };
85
- // Save config
86
- const configDir = path.dirname(configPath);
87
- await fs.mkdir(configDir, { recursive: true });
88
- await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
89
- console.log(chalk.green(`\n✓ Configuration saved to ${configPath}\n`));
78
+ // Update GitHub config at top level
79
+ config.github = config.github || {};
80
+ config.github.organization = org;
81
+ config.github.project = repo;
82
+ config.github.app = {
83
+ id: appId.toString(),
84
+ installation_id: installationId.toString(),
85
+ private_key_path: privateKeyPath,
86
+ created_via: 'manual',
87
+ created_at: new Date().toISOString(),
88
+ };
89
+ // Write config as YAML
90
+ writeYamlConfig(config);
91
+ console.log(chalk.green(`\n✓ Configuration saved to ${getConfigPath()}\n`));
90
92
  console.log('Test the configuration with:');
91
93
  console.log(chalk.cyan(` fractary-faber work issue fetch 1\n`));
92
94
  }
@@ -119,8 +121,8 @@ async function runSetup(options) {
119
121
  console.log(` Organization: ${chalk.cyan(org)}`);
120
122
  console.log(` Repository: ${chalk.cyan(repo)}\n`);
121
123
  // Step 2: Check if already configured
122
- const configPath = options.configPath || '.fractary/settings.json';
123
- const existingConfig = await checkExistingConfig(configPath);
124
+ const configPath = options.configPath || getConfigPath();
125
+ const existingConfig = await checkExistingConfig();
124
126
  if (existingConfig) {
125
127
  console.log(chalk.yellow('⚠️ GitHub App already configured in ' + configPath));
126
128
  console.log('This will create a new app and replace the existing configuration.\n');
@@ -403,18 +405,17 @@ async function runSetup(options) {
403
405
  else {
404
406
  console.error(chalk.red('\n❌ Unknown error updating config\n'));
405
407
  }
406
- console.log('You can manually update .fractary/settings.json with:');
407
- console.log(JSON.stringify({
408
- github: {
409
- organization: org,
410
- project: repo,
411
- app: {
412
- id: conversionResponse.id.toString(),
413
- installation_id: installationId,
414
- private_key_path: `~/.github/faber-${org}.pem`,
415
- },
416
- },
417
- }, null, 2));
408
+ console.log('You can manually update .fractary/config.yaml with:');
409
+ console.log(chalk.cyan(`
410
+ version: "2.0"
411
+ github:
412
+ organization: ${org}
413
+ project: ${repo}
414
+ app:
415
+ id: "${conversionResponse.id}"
416
+ installation_id: "${installationId}"
417
+ private_key_path: ~/.github/faber-${org}.pem
418
+ `));
418
419
  process.exit(1);
419
420
  }
420
421
  console.log(chalk.green(`✓ Configuration saved to ${configPath}\n`));
@@ -444,11 +445,10 @@ async function runSetup(options) {
444
445
  /**
445
446
  * Check if configuration already exists
446
447
  */
447
- async function checkExistingConfig(configPath) {
448
+ async function checkExistingConfig() {
448
449
  try {
449
- const content = await fs.readFile(configPath, 'utf-8');
450
- const config = JSON.parse(content);
451
- return !!(config.github?.app);
450
+ const config = loadYamlConfig();
451
+ return !!(config?.github?.app);
452
452
  }
453
453
  catch {
454
454
  return false;
@@ -458,15 +458,11 @@ async function checkExistingConfig(configPath) {
458
458
  * Update configuration file
459
459
  */
460
460
  async function updateConfig(configPath, appConfig) {
461
- let config = {};
462
- try {
463
- const content = await fs.readFile(configPath, 'utf-8');
464
- config = JSON.parse(content);
465
- }
466
- catch {
467
- // File doesn't exist, start with empty config
468
- }
469
- // Update GitHub config
461
+ // Load existing config or create new one
462
+ let config = loadYamlConfig() || {
463
+ version: '2.0',
464
+ };
465
+ // Update GitHub config at top level
470
466
  config.github = config.github || {};
471
467
  config.github.organization = appConfig.org;
472
468
  config.github.project = appConfig.repo;
@@ -477,14 +473,8 @@ async function updateConfig(configPath, appConfig) {
477
473
  created_via: 'manifest-flow',
478
474
  created_at: new Date().toISOString(),
479
475
  };
480
- // Ensure directory exists
481
- const lastSlash = configPath.lastIndexOf('/');
482
- if (lastSlash > 0) {
483
- const dir = configPath.substring(0, lastSlash);
484
- await fs.mkdir(dir, { recursive: true });
485
- }
486
- // Write config
487
- await fs.writeFile(configPath, JSON.stringify(config, null, 2) + '\n');
476
+ // Write config as YAML
477
+ writeYamlConfig(config);
488
478
  }
489
479
  /**
490
480
  * Create the main auth command
@@ -1,5 +1,8 @@
1
1
  /**
2
- * Init command - Initialize a new FABER project
2
+ * Init command - Initialize FABER section in unified config.yaml
3
+ *
4
+ * This command ONLY manages the faber: section of .fractary/config.yaml
5
+ * It assumes shared configuration (github, anthropic) is already set up via fractary-core:init
3
6
  */
4
7
  import { Command } from 'commander';
5
8
  export declare function createInitCommand(): Command;
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,iBAAiB,IAAI,OAAO,CAkG3C"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAcpC,wBAAgB,iBAAiB,IAAI,OAAO,CA0J3C"}
@@ -1,90 +1,147 @@
1
1
  /**
2
- * Init command - Initialize a new FABER project
2
+ * Init command - Initialize FABER section in unified config.yaml
3
+ *
4
+ * This command ONLY manages the faber: section of .fractary/config.yaml
5
+ * It assumes shared configuration (github, anthropic) is already set up via fractary-core:init
3
6
  */
4
7
  import { Command } from 'commander';
5
- import * as fs from 'fs/promises';
6
- import * as path from 'path';
7
8
  import chalk from 'chalk';
8
- import { prompt } from '../utils/prompt.js';
9
- import { createPriorityLabels, isGitHubCLIAvailable } from '../utils/labels.js';
9
+ import * as readline from 'readline/promises';
10
+ import os from 'os';
11
+ import path from 'path';
12
+ import { loadYamlConfig, writeYamlConfig, configExists, oldSettingsExists, getConfigPath, getOldSettingsPath } from '../lib/yaml-config.js';
10
13
  export function createInitCommand() {
11
- return new Command('workflow-init')
12
- .description('Initialize a new FABER project')
13
- .option('--preset <name>', 'Use a preset configuration', 'default')
14
- .option('--force', 'Overwrite existing configuration')
14
+ return new Command('init')
15
+ .description('Initialize FABER section in unified config.yaml')
16
+ .option('--force', 'Overwrite existing FABER configuration')
15
17
  .option('--json', 'Output as JSON')
16
18
  .action(async (options) => {
17
19
  try {
18
- const configDir = '.fractary/faber';
19
- const configPath = path.join(configDir, 'config.json');
20
- // Check if already initialized
21
- const exists = await fs.access(configPath).then(() => true).catch(() => false);
22
- if (exists && !options.force) {
20
+ if (!options.json) {
21
+ console.log(chalk.cyan('Initializing FABER configuration...'));
22
+ console.log();
23
+ }
24
+ // Step 1: Check for old settings.json and suggest migration
25
+ if (oldSettingsExists()) {
26
+ const oldPath = getOldSettingsPath();
23
27
  if (options.json) {
24
28
  console.log(JSON.stringify({
25
29
  status: 'error',
26
- error: { code: 'ALREADY_INITIALIZED', message: 'FABER already initialized. Use --force to reinitialize.' },
30
+ error: {
31
+ message: 'Old settings.json found',
32
+ oldPath,
33
+ migration: 'Run: fractary-faber migrate'
34
+ }
27
35
  }));
28
36
  }
29
37
  else {
30
- console.error(chalk.yellow('FABER already initialized. Use --force to reinitialize.'));
38
+ console.log(chalk.yellow('⚠️ Detected old .fractary/settings.json'));
39
+ console.log(`Found: ${chalk.gray(oldPath)}`);
40
+ console.log();
41
+ console.log('Run this command to migrate to the new format:');
42
+ console.log(chalk.cyan(' fractary-faber migrate'));
43
+ console.log();
31
44
  }
32
45
  process.exit(1);
33
46
  }
34
- // Create directory structure
35
- await fs.mkdir(configDir, { recursive: true });
36
- await fs.mkdir(path.join(configDir, 'specs'), { recursive: true });
37
- await fs.mkdir(path.join(configDir, 'logs'), { recursive: true });
38
- await fs.mkdir(path.join(configDir, 'state'), { recursive: true });
39
- // Create default configuration
40
- const config = createDefaultConfig(options.preset);
41
- await fs.writeFile(configPath, JSON.stringify(config, null, 2));
42
- // Create .gitignore for sensitive files
43
- const gitignore = `# FABER state files
44
- state/*.json
45
- logs/session-*.md
46
- *.tmp
47
- `;
48
- await fs.writeFile(path.join(configDir, '.gitignore'), gitignore);
49
- // Offer to create priority labels (if not in JSON mode)
50
- let labelsCreated = false;
51
- if (!options.json) {
52
- const ghAvailable = await isGitHubCLIAvailable();
53
- if (ghAvailable) {
54
- console.log('');
55
- const createLabels = await prompt('Create priority labels (priority-1 through priority-4) for backlog management? [Y/n]: ');
56
- if (!createLabels || createLabels.toLowerCase() === 'y' || createLabels.toLowerCase() === 'yes') {
57
- console.log(chalk.cyan('\n→ Creating priority labels...'));
58
- const result = await createPriorityLabels('priority', false);
59
- if (result.created.length > 0) {
60
- labelsCreated = true;
61
- }
62
- if (result.errors.length > 0) {
63
- console.log(chalk.yellow('\n⚠️ Some labels could not be created. You can create them manually later.'));
47
+ // Step 2: Check if config.yaml exists
48
+ if (!configExists()) {
49
+ if (options.json) {
50
+ console.log(JSON.stringify({
51
+ status: 'error',
52
+ error: {
53
+ message: 'No .fractary/config.yaml found',
54
+ recommendation: 'Run fractary-core:init first'
64
55
  }
65
- }
56
+ }));
57
+ }
58
+ else {
59
+ console.error(chalk.red('Error: No .fractary/config.yaml found'));
60
+ console.log();
61
+ console.log('FABER requires shared configuration to be set up first.');
62
+ console.log();
63
+ console.log('Please run one of the following:');
64
+ console.log(chalk.cyan(' 1. fractary-core:init') + ' - Initialize unified config with GitHub/Anthropic settings');
65
+ console.log(chalk.cyan(' 2. fractary-faber migrate') + ' - Migrate from old .fractary/settings.json');
66
+ console.log();
67
+ }
68
+ process.exit(1);
69
+ }
70
+ // Step 3: Load existing config
71
+ const config = loadYamlConfig();
72
+ if (!config) {
73
+ throw new Error('Failed to load .fractary/config.yaml');
74
+ }
75
+ // Step 4: Check if FABER section already exists
76
+ if (config.faber && !options.force) {
77
+ if (options.json) {
78
+ console.log(JSON.stringify({
79
+ status: 'exists',
80
+ path: getConfigPath(),
81
+ message: 'FABER section already configured'
82
+ }));
83
+ }
84
+ else {
85
+ console.log(chalk.yellow('FABER section already exists in config.yaml'));
86
+ console.log();
87
+ console.log('Use --force to overwrite existing configuration.');
88
+ console.log();
89
+ }
90
+ return;
91
+ }
92
+ // Step 5: Validate shared sections and warn if missing
93
+ const warnings = [];
94
+ if (!config.github) {
95
+ warnings.push('Missing \'github\' section - FABER needs GitHub authentication');
96
+ }
97
+ if (!config.anthropic) {
98
+ warnings.push('Missing \'anthropic\' section - FABER needs Anthropic API key');
99
+ }
100
+ if (warnings.length > 0 && !options.json) {
101
+ console.log(chalk.yellow('⚠️ Configuration warnings:'));
102
+ warnings.forEach(w => console.log(chalk.yellow(` - ${w}`)));
103
+ console.log();
104
+ console.log(chalk.bold('Recommendation:') + ' Run ' + chalk.cyan('fractary-core:init') + ' to set up shared configuration');
105
+ console.log();
106
+ // Ask if user wants to continue
107
+ const rl = readline.createInterface({
108
+ input: process.stdin,
109
+ output: process.stdout,
110
+ });
111
+ const answer = await rl.question('Continue with FABER initialization? (y/N) ');
112
+ rl.close();
113
+ console.log();
114
+ if (answer.toLowerCase() !== 'y') {
115
+ console.log('Initialization cancelled.');
116
+ return;
66
117
  }
67
118
  }
119
+ // Step 6: Initialize FABER section
120
+ config.faber = {
121
+ worktree: {
122
+ location: path.join(os.homedir(), '.claude-worktrees'),
123
+ inherit_from_claude: true
124
+ },
125
+ workflow: {
126
+ config_path: path.join('.fractary', 'faber', 'workflows')
127
+ }
128
+ };
129
+ // Step 7: Write config back
130
+ writeYamlConfig(config);
68
131
  if (options.json) {
69
132
  console.log(JSON.stringify({
70
133
  status: 'success',
71
- data: { configPath, preset: options.preset, labelsCreated },
72
- }, null, 2));
134
+ path: getConfigPath()
135
+ }));
73
136
  }
74
137
  else {
75
- console.log(chalk.green('\n✓ FABER initialized successfully'));
76
- console.log(chalk.gray(` Config: ${configPath}`));
77
- console.log(chalk.gray(` Preset: ${options.preset}`));
78
- if (labelsCreated) {
79
- console.log(chalk.gray(' Priority labels: Created'));
80
- }
81
- console.log('\nNext steps:');
82
- console.log(' 1. Configure work tracking: Edit .fractary/faber/config.json');
83
- console.log(' 2. Start a workflow: fractary-faber run --work-id <issue-number>');
84
- console.log(' 3. Check status: fractary-faber status');
85
- if (labelsCreated) {
86
- console.log(' 4. Use priority labels: gh issue edit <number> --add-label "priority-1"');
87
- }
138
+ console.log(chalk.green('✓ FABER configuration initialized in .fractary/config.yaml'));
139
+ console.log();
140
+ console.log('Next steps:');
141
+ console.log(' 1. Set up GitHub App authentication: ' + chalk.cyan('fractary-faber auth setup'));
142
+ console.log(' 2. Create workflow configuration files in .fractary/faber/workflows/');
143
+ console.log(' 3. Run your first workflow: ' + chalk.cyan('fractary-faber workflow plan <issue-number>'));
144
+ console.log();
88
145
  }
89
146
  }
90
147
  catch (error) {
@@ -92,7 +149,7 @@ logs/session-*.md
92
149
  if (options.json) {
93
150
  console.error(JSON.stringify({
94
151
  status: 'error',
95
- error: { code: 'INIT_ERROR', message },
152
+ error: { message }
96
153
  }));
97
154
  }
98
155
  else {
@@ -102,89 +159,3 @@ logs/session-*.md
102
159
  }
103
160
  });
104
161
  }
105
- /**
106
- * Create default FABER configuration
107
- */
108
- function createDefaultConfig(preset) {
109
- const baseConfig = {
110
- version: '1.3.2',
111
- preset,
112
- // Work tracking configuration
113
- work: {
114
- provider: 'github', // or 'jira', 'linear'
115
- },
116
- // Repository configuration
117
- repo: {
118
- provider: 'github', // or 'gitlab', 'bitbucket'
119
- defaultBranch: 'main',
120
- branchPrefix: 'feat/',
121
- conventionalCommits: true,
122
- },
123
- // Specification configuration
124
- spec: {
125
- directory: '.fractary/faber/specs',
126
- templates: {
127
- feature: 'feature',
128
- bugfix: 'bugfix',
129
- refactor: 'refactor',
130
- },
131
- },
132
- // Log configuration
133
- logs: {
134
- directory: '.fractary/faber/logs',
135
- retention: {
136
- session: 30,
137
- build: 90,
138
- deployment: 365,
139
- },
140
- },
141
- // Workflow configuration
142
- workflow: {
143
- defaultAutonomy: 'guarded',
144
- phases: ['frame', 'architect', 'build', 'evaluate', 'release'],
145
- checkpoints: true,
146
- },
147
- // State management
148
- state: {
149
- directory: '.fractary/faber/state',
150
- persistence: 'file', // or 'none' for stateless
151
- },
152
- };
153
- // Apply preset modifications
154
- switch (preset) {
155
- case 'minimal':
156
- return {
157
- ...baseConfig,
158
- workflow: {
159
- ...baseConfig.workflow,
160
- checkpoints: false,
161
- },
162
- logs: {
163
- ...baseConfig.logs,
164
- retention: {
165
- session: 7,
166
- build: 30,
167
- deployment: 90,
168
- },
169
- },
170
- };
171
- case 'enterprise':
172
- return {
173
- ...baseConfig,
174
- workflow: {
175
- ...baseConfig.workflow,
176
- defaultAutonomy: 'assist',
177
- },
178
- logs: {
179
- ...baseConfig.logs,
180
- retention: {
181
- session: 90,
182
- build: 365,
183
- deployment: 730,
184
- },
185
- },
186
- };
187
- default:
188
- return baseConfig;
189
- }
190
- }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Migrate command - Convert .fractary/settings.json to .fractary/config.yaml
3
+ *
4
+ * Migrates old FABER configuration to the unified config format.
5
+ * After migration, settings.json is backed up and all FABER commands use config.yaml.
6
+ */
7
+ import { Command } from 'commander';
8
+ export declare function createMigrateCommand(): Command;
9
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsDpC,wBAAgB,oBAAoB,IAAI,OAAO,CAgN9C"}