@fractary/faber-cli 1.3.1 → 1.3.3
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/README.md +101 -4
- package/dist/commands/auth/index.d.ts +13 -0
- package/dist/commands/auth/index.d.ts.map +1 -0
- package/dist/commands/auth/index.js +291 -0
- package/dist/commands/init.js +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/lib/anthropic-client.js +1 -1
- package/dist/lib/github-app-auth.d.ts +122 -0
- package/dist/lib/github-app-auth.d.ts.map +1 -0
- package/dist/lib/github-app-auth.js +294 -0
- package/dist/lib/github-app-setup.d.ts +131 -0
- package/dist/lib/github-app-setup.d.ts.map +1 -0
- package/dist/lib/github-app-setup.js +241 -0
- package/dist/lib/repo-client.d.ts +22 -2
- package/dist/lib/repo-client.d.ts.map +1 -1
- package/dist/lib/repo-client.js +52 -8
- package/dist/lib/sdk-config-adapter.d.ts +52 -0
- package/dist/lib/sdk-config-adapter.d.ts.map +1 -1
- package/dist/lib/sdk-config-adapter.js +167 -6
- package/dist/types/config.d.ts +12 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/utils/github-manifest.d.ts +39 -0
- package/dist/utils/github-manifest.d.ts.map +1 -0
- package/dist/utils/github-manifest.js +84 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -16,7 +16,40 @@ npx @fractary/faber-cli --help
|
|
|
16
16
|
|
|
17
17
|
## Quick Start
|
|
18
18
|
|
|
19
|
-
###
|
|
19
|
+
### 1. Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g @fractary/faber-cli
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or use directly with `npx`:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx @fractary/faber-cli --help
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 2. Authenticate with GitHub
|
|
32
|
+
|
|
33
|
+
**Option A: Automated Setup (Recommended)**
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd your-project
|
|
37
|
+
fractary-faber auth setup
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This command will:
|
|
41
|
+
1. Detect your GitHub organization and repository
|
|
42
|
+
2. Show you a URL to create a GitHub App
|
|
43
|
+
3. Guide you through copying the authorization code
|
|
44
|
+
4. Automatically configure FABER CLI
|
|
45
|
+
|
|
46
|
+
All in ~30 seconds!
|
|
47
|
+
|
|
48
|
+
**Option B: Manual Setup**
|
|
49
|
+
|
|
50
|
+
See [GitHub App Setup Guide](../docs/github-app-setup.md) for detailed manual instructions.
|
|
51
|
+
|
|
52
|
+
### 3. Initialize a FABER project
|
|
20
53
|
|
|
21
54
|
```bash
|
|
22
55
|
fractary-faber init
|
|
@@ -191,14 +224,78 @@ All commands support:
|
|
|
191
224
|
- `--debug` - Enable debug output
|
|
192
225
|
- `--help` - Show command help
|
|
193
226
|
|
|
194
|
-
##
|
|
227
|
+
## Authentication
|
|
228
|
+
|
|
229
|
+
### GitHub App Authentication (Recommended)
|
|
195
230
|
|
|
196
|
-
|
|
231
|
+
For enhanced security, audit trails, and enterprise readiness, use GitHub App authentication instead of Personal Access Tokens.
|
|
232
|
+
|
|
233
|
+
**Quick Setup (Automated):**
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
fractary-faber auth setup
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
This command will guide you through creating and configuring a GitHub App in ~30 seconds.
|
|
240
|
+
|
|
241
|
+
**Manual Configuration (`.fractary/settings.json`):**
|
|
242
|
+
```json
|
|
243
|
+
{
|
|
244
|
+
"github": {
|
|
245
|
+
"organization": "your-org",
|
|
246
|
+
"project": "your-repo",
|
|
247
|
+
"app": {
|
|
248
|
+
"id": "123456",
|
|
249
|
+
"installation_id": "12345678",
|
|
250
|
+
"private_key_path": "~/.github/faber-app.pem"
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**For CI/CD (environment variable):**
|
|
257
|
+
```json
|
|
258
|
+
{
|
|
259
|
+
"github": {
|
|
260
|
+
"organization": "your-org",
|
|
261
|
+
"project": "your-repo",
|
|
262
|
+
"app": {
|
|
263
|
+
"id": "123456",
|
|
264
|
+
"installation_id": "12345678",
|
|
265
|
+
"private_key_env_var": "GITHUB_APP_PRIVATE_KEY"
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
export GITHUB_APP_PRIVATE_KEY=$(cat ~/.github/faber-app.pem | base64)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**See detailed setup guide:** [docs/github-app-setup.md](../docs/github-app-setup.md)
|
|
276
|
+
|
|
277
|
+
### Personal Access Token (Legacy)
|
|
278
|
+
|
|
279
|
+
Still supported for backward compatibility:
|
|
197
280
|
|
|
198
281
|
```bash
|
|
199
|
-
# GitHub
|
|
200
282
|
export GITHUB_TOKEN=<token>
|
|
283
|
+
```
|
|
201
284
|
|
|
285
|
+
Or in `.fractary/settings.json`:
|
|
286
|
+
```json
|
|
287
|
+
{
|
|
288
|
+
"github": {
|
|
289
|
+
"token": "ghp_xxxxxxxxxxxx",
|
|
290
|
+
"organization": "your-org",
|
|
291
|
+
"project": "your-repo"
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Other Providers
|
|
297
|
+
|
|
298
|
+
```bash
|
|
202
299
|
# Jira
|
|
203
300
|
export JIRA_BASE_URL=<url>
|
|
204
301
|
export JIRA_USERNAME=<username>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication setup command
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
/**
|
|
6
|
+
* Create the auth setup command
|
|
7
|
+
*/
|
|
8
|
+
export declare function createAuthSetupCommand(): Command;
|
|
9
|
+
/**
|
|
10
|
+
* Create the main auth command
|
|
11
|
+
*/
|
|
12
|
+
export declare function createAuthCommand(): Command;
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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;AA4BpC;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAehD;AAwVD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAM3C"}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication setup command
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import * as readline from 'readline/promises';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
import { generateAppManifest, getManifestCreationUrl, exchangeCodeForCredentials, validateAppCredentials, getInstallationId, savePrivateKey, formatPermissionsDisplay, } from '../../lib/github-app-setup.js';
|
|
9
|
+
import { parseCodeFromUrl, validateManifestCode, detectGitHubContext, isGitRepository, } from '../../utils/github-manifest.js';
|
|
10
|
+
/**
|
|
11
|
+
* Create the auth setup command
|
|
12
|
+
*/
|
|
13
|
+
export function createAuthSetupCommand() {
|
|
14
|
+
return new Command('setup')
|
|
15
|
+
.description('Set up GitHub App authentication for FABER CLI')
|
|
16
|
+
.option('--org <name>', 'GitHub organization name')
|
|
17
|
+
.option('--repo <name>', 'GitHub repository name')
|
|
18
|
+
.option('--config-path <path>', 'Path to config file', '.fractary/settings.json')
|
|
19
|
+
.option('--show-manifest', 'Display manifest JSON before setup')
|
|
20
|
+
.option('--no-save', 'Display credentials without saving')
|
|
21
|
+
.action(async (options) => {
|
|
22
|
+
await runSetup(options);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Run the setup flow
|
|
27
|
+
*/
|
|
28
|
+
async function runSetup(options) {
|
|
29
|
+
console.log(chalk.bold('\n🔐 GitHub App Authentication Setup\n'));
|
|
30
|
+
// Step 1: Detect or prompt for GitHub context
|
|
31
|
+
let org = options.org;
|
|
32
|
+
let repo = options.repo;
|
|
33
|
+
if (!org || !repo) {
|
|
34
|
+
if (!isGitRepository()) {
|
|
35
|
+
console.error(chalk.red('❌ Error: Not a git repository\n') +
|
|
36
|
+
'Run this command from a git repository or provide --org and --repo flags.');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
const context = detectGitHubContext();
|
|
40
|
+
if (!context) {
|
|
41
|
+
console.error(chalk.red('❌ Error: Could not detect GitHub organization/repository\n') +
|
|
42
|
+
'Please provide --org and --repo flags.');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
org = context.org;
|
|
46
|
+
repo = context.repo;
|
|
47
|
+
}
|
|
48
|
+
console.log('Detected GitHub context:');
|
|
49
|
+
console.log(` Organization: ${chalk.cyan(org)}`);
|
|
50
|
+
console.log(` Repository: ${chalk.cyan(repo)}\n`);
|
|
51
|
+
// Step 2: Check if already configured
|
|
52
|
+
const configPath = options.configPath || '.fractary/settings.json';
|
|
53
|
+
const existingConfig = await checkExistingConfig(configPath);
|
|
54
|
+
if (existingConfig) {
|
|
55
|
+
console.log(chalk.yellow('⚠️ GitHub App already configured in ' + configPath));
|
|
56
|
+
console.log('This will create a new app and replace the existing configuration.\n');
|
|
57
|
+
const rl = readline.createInterface({
|
|
58
|
+
input: process.stdin,
|
|
59
|
+
output: process.stdout,
|
|
60
|
+
});
|
|
61
|
+
const answer = await rl.question('Continue? (y/N): ');
|
|
62
|
+
rl.close();
|
|
63
|
+
if (answer.toLowerCase() !== 'y') {
|
|
64
|
+
console.log('Setup cancelled.');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
console.log();
|
|
68
|
+
}
|
|
69
|
+
// Step 3: Generate manifest
|
|
70
|
+
const manifest = generateAppManifest({ organization: org, repository: repo });
|
|
71
|
+
if (options.showManifest) {
|
|
72
|
+
console.log(chalk.bold('\n📋 App Manifest:\n'));
|
|
73
|
+
console.log(JSON.stringify(manifest, null, 2));
|
|
74
|
+
console.log();
|
|
75
|
+
}
|
|
76
|
+
// Step 4: Display creation instructions
|
|
77
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
78
|
+
console.log(chalk.bold('📋 STEP 1: Create the GitHub App'));
|
|
79
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
80
|
+
console.log();
|
|
81
|
+
const creationUrl = getManifestCreationUrl(manifest);
|
|
82
|
+
console.log('Please click this URL to create your GitHub App:\n');
|
|
83
|
+
console.log(chalk.cyan.bold(`👉 ${creationUrl}\n`));
|
|
84
|
+
console.log('The app will request these permissions:');
|
|
85
|
+
console.log(formatPermissionsDisplay(manifest));
|
|
86
|
+
console.log();
|
|
87
|
+
const rl1 = readline.createInterface({
|
|
88
|
+
input: process.stdin,
|
|
89
|
+
output: process.stdout,
|
|
90
|
+
});
|
|
91
|
+
await rl1.question('Press Enter when you have created the app...');
|
|
92
|
+
rl1.close();
|
|
93
|
+
// Step 5: Prompt for code
|
|
94
|
+
console.log();
|
|
95
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
96
|
+
console.log(chalk.bold('📋 STEP 2: Copy the code from the redirect URL'));
|
|
97
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
98
|
+
console.log();
|
|
99
|
+
console.log('After creating the app, GitHub will redirect you to a URL like:');
|
|
100
|
+
console.log(chalk.gray('https://github.com/settings/apps/your-app?code=XXXXXXXXXXXXX'));
|
|
101
|
+
console.log();
|
|
102
|
+
console.log('Copy the entire code from the URL bar and paste it below:\n');
|
|
103
|
+
let code = null;
|
|
104
|
+
let attempts = 0;
|
|
105
|
+
const maxAttempts = 3;
|
|
106
|
+
const rl2 = readline.createInterface({
|
|
107
|
+
input: process.stdin,
|
|
108
|
+
output: process.stdout,
|
|
109
|
+
});
|
|
110
|
+
while (!code && attempts < maxAttempts) {
|
|
111
|
+
const input = await rl2.question(chalk.bold('Code: '));
|
|
112
|
+
// Try to parse code from URL or use directly
|
|
113
|
+
code = parseCodeFromUrl(input);
|
|
114
|
+
if (!code || !validateManifestCode(code)) {
|
|
115
|
+
attempts++;
|
|
116
|
+
if (attempts < maxAttempts) {
|
|
117
|
+
console.log(chalk.red(`\n❌ Invalid code format. Please try again (${maxAttempts - attempts} attempts remaining).\n`));
|
|
118
|
+
code = null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
rl2.close();
|
|
123
|
+
if (!code) {
|
|
124
|
+
console.error(chalk.red('\n❌ Error: Could not validate code after multiple attempts.\n'));
|
|
125
|
+
console.log('Please run the command again and ensure you copy the complete code.');
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
// Step 6: Exchange code for credentials
|
|
129
|
+
console.log(chalk.gray('\nExchanging code for credentials...'));
|
|
130
|
+
let conversionResponse;
|
|
131
|
+
try {
|
|
132
|
+
conversionResponse = await exchangeCodeForCredentials(code);
|
|
133
|
+
validateAppCredentials(conversionResponse);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
if (error instanceof Error) {
|
|
137
|
+
console.error(chalk.red('\n❌ Error: ' + error.message + '\n'));
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
console.error(chalk.red('\n❌ Unknown error occurred\n'));
|
|
141
|
+
}
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
console.log(chalk.green('✓ App created successfully!'));
|
|
145
|
+
console.log(` App ID: ${chalk.cyan(conversionResponse.id)}`);
|
|
146
|
+
console.log(` App Name: ${chalk.cyan(conversionResponse.name)}\n`);
|
|
147
|
+
// Step 7: Fetch installation ID
|
|
148
|
+
console.log(chalk.gray('Fetching installation ID...'));
|
|
149
|
+
let installationId;
|
|
150
|
+
try {
|
|
151
|
+
installationId = await getInstallationId(conversionResponse.id.toString(), conversionResponse.pem, org);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (error instanceof Error) {
|
|
155
|
+
console.error(chalk.red('\n❌ Error: ' + error.message + '\n'));
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
console.error(chalk.red('\n❌ Unknown error occurred\n'));
|
|
159
|
+
}
|
|
160
|
+
console.log('The app was created but could not find the installation.');
|
|
161
|
+
console.log(`Please install the app on your organization: ${chalk.cyan(`https://github.com/apps/${conversionResponse.slug}/installations/new`)}`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
console.log(chalk.green(`✓ Installation ID: ${chalk.cyan(installationId)}\n`));
|
|
165
|
+
// Step 8: Save private key
|
|
166
|
+
if (!options.noSave) {
|
|
167
|
+
console.log(chalk.gray(`Saving private key to ~/.github/faber-${org}.pem...`));
|
|
168
|
+
let keyPath;
|
|
169
|
+
try {
|
|
170
|
+
keyPath = await savePrivateKey(conversionResponse.pem, org);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (error instanceof Error) {
|
|
174
|
+
console.error(chalk.red('\n❌ Error saving private key: ' + error.message + '\n'));
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
console.error(chalk.red('\n❌ Unknown error saving private key\n'));
|
|
178
|
+
}
|
|
179
|
+
console.log('You can manually save the private key to ~/.github/ directory.');
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
console.log(chalk.green(`✓ Private key saved (permissions: 0600)\n`));
|
|
183
|
+
// Step 9: Update configuration
|
|
184
|
+
console.log(chalk.gray('Updating configuration...'));
|
|
185
|
+
try {
|
|
186
|
+
await updateConfig(configPath, {
|
|
187
|
+
id: conversionResponse.id.toString(),
|
|
188
|
+
installation_id: installationId,
|
|
189
|
+
private_key_path: keyPath.replace(process.env.HOME || '', '~'),
|
|
190
|
+
org,
|
|
191
|
+
repo,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
if (error instanceof Error) {
|
|
196
|
+
console.error(chalk.red('\n❌ Error updating config: ' + error.message + '\n'));
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.error(chalk.red('\n❌ Unknown error updating config\n'));
|
|
200
|
+
}
|
|
201
|
+
console.log('You can manually update .fractary/settings.json with:');
|
|
202
|
+
console.log(JSON.stringify({
|
|
203
|
+
github: {
|
|
204
|
+
organization: org,
|
|
205
|
+
project: repo,
|
|
206
|
+
app: {
|
|
207
|
+
id: conversionResponse.id.toString(),
|
|
208
|
+
installation_id: installationId,
|
|
209
|
+
private_key_path: `~/.github/faber-${org}.pem`,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
}, null, 2));
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
console.log(chalk.green(`✓ Configuration saved to ${configPath}\n`));
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
// Display credentials without saving
|
|
219
|
+
console.log(chalk.bold('\n📋 App Credentials:\n'));
|
|
220
|
+
console.log(JSON.stringify({
|
|
221
|
+
app_id: conversionResponse.id,
|
|
222
|
+
installation_id: installationId,
|
|
223
|
+
app_slug: conversionResponse.slug,
|
|
224
|
+
private_key: '[PEM key not shown]',
|
|
225
|
+
}, null, 2));
|
|
226
|
+
}
|
|
227
|
+
// Step 10: Success message
|
|
228
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
229
|
+
console.log(chalk.bold.green('✨ Setup Complete!'));
|
|
230
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
231
|
+
console.log();
|
|
232
|
+
console.log('Test your configuration:');
|
|
233
|
+
console.log(chalk.cyan(' fractary-faber work issue fetch 1'));
|
|
234
|
+
console.log();
|
|
235
|
+
console.log('View your app:');
|
|
236
|
+
console.log(chalk.cyan(` https://github.com/organizations/${org}/settings/apps/${conversionResponse.slug}`));
|
|
237
|
+
console.log();
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Check if configuration already exists
|
|
241
|
+
*/
|
|
242
|
+
async function checkExistingConfig(configPath) {
|
|
243
|
+
try {
|
|
244
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
245
|
+
const config = JSON.parse(content);
|
|
246
|
+
return !!(config.github?.app);
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Update configuration file
|
|
254
|
+
*/
|
|
255
|
+
async function updateConfig(configPath, appConfig) {
|
|
256
|
+
let config = {};
|
|
257
|
+
try {
|
|
258
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
259
|
+
config = JSON.parse(content);
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// File doesn't exist, start with empty config
|
|
263
|
+
}
|
|
264
|
+
// Update GitHub config
|
|
265
|
+
config.github = config.github || {};
|
|
266
|
+
config.github.organization = appConfig.org;
|
|
267
|
+
config.github.project = appConfig.repo;
|
|
268
|
+
config.github.app = {
|
|
269
|
+
id: appConfig.id,
|
|
270
|
+
installation_id: appConfig.installation_id,
|
|
271
|
+
private_key_path: appConfig.private_key_path,
|
|
272
|
+
created_via: 'manifest-flow',
|
|
273
|
+
created_at: new Date().toISOString(),
|
|
274
|
+
};
|
|
275
|
+
// Ensure directory exists
|
|
276
|
+
const lastSlash = configPath.lastIndexOf('/');
|
|
277
|
+
if (lastSlash > 0) {
|
|
278
|
+
const dir = configPath.substring(0, lastSlash);
|
|
279
|
+
await fs.mkdir(dir, { recursive: true });
|
|
280
|
+
}
|
|
281
|
+
// Write config
|
|
282
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Create the main auth command
|
|
286
|
+
*/
|
|
287
|
+
export function createAuthCommand() {
|
|
288
|
+
const command = new Command('auth').description('Authentication management');
|
|
289
|
+
command.addCommand(createAuthSetupCommand());
|
|
290
|
+
return command;
|
|
291
|
+
}
|
package/dist/commands/init.js
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC;;GAEG;AACH,wBAAgB,cAAc,IAAI,OAAO,CA4IxC"}
|
package/dist/index.js
CHANGED
|
@@ -14,14 +14,15 @@ import { createSpecCommand } from './commands/spec/index.js';
|
|
|
14
14
|
import { createLogsCommand } from './commands/logs/index.js';
|
|
15
15
|
import { createInitCommand } from './commands/init.js';
|
|
16
16
|
import { createPlanCommand } from './commands/plan/index.js';
|
|
17
|
-
|
|
17
|
+
import { createAuthCommand } from './commands/auth/index.js';
|
|
18
|
+
const version = '1.3.2';
|
|
18
19
|
/**
|
|
19
20
|
* Create and configure the main CLI program
|
|
20
21
|
*/
|
|
21
22
|
export function createFaberCLI() {
|
|
22
23
|
const program = new Command('fractary-faber');
|
|
23
24
|
program
|
|
24
|
-
.description('FABER development toolkit (workflow, work, repo, spec, logs)')
|
|
25
|
+
.description('FABER development toolkit (workflow, work, repo, spec, logs, auth)')
|
|
25
26
|
.version(version)
|
|
26
27
|
.enablePositionalOptions();
|
|
27
28
|
// Global options
|
|
@@ -116,6 +117,7 @@ export function createFaberCLI() {
|
|
|
116
117
|
cleanupCmd.parse(['', '', ...Object.entries(options).flatMap(([k, v]) => typeof v === 'boolean' && v ? [`--${k}`] : typeof v === 'string' ? [`--${k}`, v] : [])], { from: 'user' });
|
|
117
118
|
});
|
|
118
119
|
// Subcommand trees
|
|
120
|
+
program.addCommand(createAuthCommand());
|
|
119
121
|
program.addCommand(createWorkCommand());
|
|
120
122
|
program.addCommand(createRepoCommand());
|
|
121
123
|
program.addCommand(createSpecCommand());
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub App Authentication Module
|
|
3
|
+
*
|
|
4
|
+
* Provides JWT generation, installation token exchange, and token caching
|
|
5
|
+
* for GitHub App authentication in FABER CLI.
|
|
6
|
+
*/
|
|
7
|
+
import type { GitHubAppConfig } from '../types/config.js';
|
|
8
|
+
/**
|
|
9
|
+
* Private Key Loader
|
|
10
|
+
*
|
|
11
|
+
* Loads private keys from file path or environment variable
|
|
12
|
+
*/
|
|
13
|
+
export declare class PrivateKeyLoader {
|
|
14
|
+
/**
|
|
15
|
+
* Load private key from configured sources.
|
|
16
|
+
* Priority: env var > file path
|
|
17
|
+
*
|
|
18
|
+
* @param config - GitHub App configuration
|
|
19
|
+
* @returns The private key content
|
|
20
|
+
* @throws Error if private key cannot be loaded
|
|
21
|
+
*/
|
|
22
|
+
static load(config: GitHubAppConfig): Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* Validate private key format.
|
|
25
|
+
*
|
|
26
|
+
* @param key - The private key content
|
|
27
|
+
* @returns true if valid PEM format
|
|
28
|
+
*/
|
|
29
|
+
static validate(key: string): boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* GitHub App Authentication
|
|
33
|
+
*
|
|
34
|
+
* Handles JWT generation, installation token exchange, and caching
|
|
35
|
+
*/
|
|
36
|
+
export declare class GitHubAppAuth {
|
|
37
|
+
private cache;
|
|
38
|
+
private config;
|
|
39
|
+
private refreshPromise;
|
|
40
|
+
private static readonly REFRESH_THRESHOLD_MS;
|
|
41
|
+
private static readonly JWT_EXPIRY_SECONDS;
|
|
42
|
+
private static readonly GITHUB_API_URL;
|
|
43
|
+
constructor(config: GitHubAppConfig);
|
|
44
|
+
/**
|
|
45
|
+
* Get a valid installation token.
|
|
46
|
+
* Returns cached token if still valid, otherwise generates new one.
|
|
47
|
+
*
|
|
48
|
+
* @returns Installation access token
|
|
49
|
+
*/
|
|
50
|
+
getToken(): Promise<string>;
|
|
51
|
+
/**
|
|
52
|
+
* Force refresh the token.
|
|
53
|
+
*
|
|
54
|
+
* @returns New installation access token
|
|
55
|
+
*/
|
|
56
|
+
refreshToken(): Promise<string>;
|
|
57
|
+
/**
|
|
58
|
+
* Check if token needs refresh (within 5 minutes of expiration).
|
|
59
|
+
*
|
|
60
|
+
* @returns true if token should be refreshed
|
|
61
|
+
*/
|
|
62
|
+
isTokenExpiringSoon(): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Validate the configuration and private key.
|
|
65
|
+
*
|
|
66
|
+
* @throws Error if configuration is invalid
|
|
67
|
+
*/
|
|
68
|
+
validate(): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Perform the actual token refresh
|
|
71
|
+
*/
|
|
72
|
+
private doRefresh;
|
|
73
|
+
/**
|
|
74
|
+
* Generate a JWT for GitHub App authentication
|
|
75
|
+
*/
|
|
76
|
+
private generateJWT;
|
|
77
|
+
/**
|
|
78
|
+
* Exchange JWT for installation access token
|
|
79
|
+
*/
|
|
80
|
+
private exchangeForInstallationToken;
|
|
81
|
+
/**
|
|
82
|
+
* Check if token is expired
|
|
83
|
+
*/
|
|
84
|
+
private isExpired;
|
|
85
|
+
/**
|
|
86
|
+
* Check if token is expiring soon
|
|
87
|
+
*/
|
|
88
|
+
private isExpiringSoon;
|
|
89
|
+
/**
|
|
90
|
+
* Trigger background token refresh (non-blocking)
|
|
91
|
+
*/
|
|
92
|
+
private triggerBackgroundRefresh;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Token Provider Interface
|
|
96
|
+
*
|
|
97
|
+
* Abstract interface for getting tokens, supporting both PAT and GitHub App
|
|
98
|
+
*/
|
|
99
|
+
export interface TokenProvider {
|
|
100
|
+
getToken(): Promise<string>;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Static Token Provider
|
|
104
|
+
*
|
|
105
|
+
* Simple provider for static PAT tokens
|
|
106
|
+
*/
|
|
107
|
+
export declare class StaticTokenProvider implements TokenProvider {
|
|
108
|
+
private token;
|
|
109
|
+
constructor(token: string);
|
|
110
|
+
getToken(): Promise<string>;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* GitHub App Token Provider
|
|
114
|
+
*
|
|
115
|
+
* Provider that uses GitHubAppAuth for dynamic token generation
|
|
116
|
+
*/
|
|
117
|
+
export declare class GitHubAppTokenProvider implements TokenProvider {
|
|
118
|
+
private auth;
|
|
119
|
+
constructor(auth: GitHubAppAuth);
|
|
120
|
+
getToken(): Promise<string>;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=github-app-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-app-auth.d.ts","sourceRoot":"","sources":["../../src/lib/github-app-auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAqB1D;;;;GAIG;AACH,qBAAa,gBAAgB;IAC3B;;;;;;;OAOG;WACU,IAAI,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;IAiD3D;;;;;OAKG;IACH,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;CAUtC;AAED;;;;GAIG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,cAAc,CAAgC;IAGtD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAiB;IAE7D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAO;IAEjD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAA4B;gBAEtD,MAAM,EAAE,eAAe;IAInC;;;;;OAKG;IACG,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAkBjC;;;;OAIG;IACG,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAerC;;;;OAIG;IACH,mBAAmB,IAAI,OAAO;IAK9B;;;;OAIG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB/B;;OAEG;YACW,SAAS;IAcvB;;OAEG;YACW,WAAW;IAoBzB;;OAEG;YACW,4BAA4B;IAwD1C;;OAEG;IACH,OAAO,CAAC,SAAS;IAIjB;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;OAEG;IACH,OAAO,CAAC,wBAAwB;CAUjC;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC7B;AAED;;;;GAIG;AACH,qBAAa,mBAAoB,YAAW,aAAa;IAC3C,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,MAAM;IAE3B,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;CAGlC;AAED;;;;GAIG;AACH,qBAAa,sBAAuB,YAAW,aAAa;IAC9C,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,aAAa;IAEjC,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;CAGlC"}
|