@cpretzinger/boss-claude 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +304 -1
- package/bin/boss-claude.js +1138 -0
- package/bin/commands/mode.js +250 -0
- package/bin/onyx-guard.js +259 -0
- package/bin/onyx-guard.sh +251 -0
- package/bin/prompts.js +284 -0
- package/bin/rollback.js +85 -0
- package/bin/setup-wizard.js +492 -0
- package/config/.env.example +17 -0
- package/lib/README.md +83 -0
- package/lib/agent-logger.js +61 -0
- package/lib/agents/memory-engineers/github-memory-engineer.js +251 -0
- package/lib/agents/memory-engineers/postgres-memory-engineer.js +633 -0
- package/lib/agents/memory-engineers/qdrant-memory-engineer.js +358 -0
- package/lib/agents/memory-engineers/redis-memory-engineer.js +383 -0
- package/lib/agents/memory-supervisor.js +526 -0
- package/lib/agents/registry.js +135 -0
- package/lib/auto-monitor.js +131 -0
- package/lib/checkpoint-hook.js +112 -0
- package/lib/checkpoint.js +319 -0
- package/lib/commentator.js +213 -0
- package/lib/context-scribe.js +120 -0
- package/lib/delegation-strategies.js +326 -0
- package/lib/hierarchy-validator.js +643 -0
- package/lib/index.js +15 -0
- package/lib/init-with-mode.js +261 -0
- package/lib/init.js +44 -6
- package/lib/memory-result-aggregator.js +252 -0
- package/lib/memory.js +35 -7
- package/lib/mode-enforcer.js +473 -0
- package/lib/onyx-banner.js +169 -0
- package/lib/onyx-identity.js +214 -0
- package/lib/onyx-monitor.js +381 -0
- package/lib/onyx-reminder.js +188 -0
- package/lib/onyx-tool-interceptor.js +341 -0
- package/lib/onyx-wrapper.js +315 -0
- package/lib/orchestrator-gate.js +334 -0
- package/lib/output-formatter.js +296 -0
- package/lib/postgres.js +1 -1
- package/lib/prompt-injector.js +220 -0
- package/lib/prompts.js +532 -0
- package/lib/session.js +153 -6
- package/lib/setup/README.md +187 -0
- package/lib/setup/env-manager.js +785 -0
- package/lib/setup/error-recovery.js +630 -0
- package/lib/setup/explain-scopes.js +385 -0
- package/lib/setup/github-instructions.js +333 -0
- package/lib/setup/github-repo.js +254 -0
- package/lib/setup/import-credentials.js +498 -0
- package/lib/setup/index.js +62 -0
- package/lib/setup/init-postgres.js +785 -0
- package/lib/setup/init-redis.js +456 -0
- package/lib/setup/integration-test.js +652 -0
- package/lib/setup/progress.js +357 -0
- package/lib/setup/rollback.js +670 -0
- package/lib/setup/rollback.test.js +452 -0
- package/lib/setup/setup-with-rollback.example.js +351 -0
- package/lib/setup/summary.js +400 -0
- package/lib/setup/test-github-setup.js +10 -0
- package/lib/setup/test-postgres-init.js +98 -0
- package/lib/setup/verify-setup.js +102 -0
- package/lib/task-agent-worker.js +235 -0
- package/lib/token-monitor.js +466 -0
- package/lib/tool-wrapper-integration.js +369 -0
- package/lib/tool-wrapper.js +387 -0
- package/lib/validators/README.md +497 -0
- package/lib/validators/config.js +583 -0
- package/lib/validators/config.test.js +175 -0
- package/lib/validators/github.js +310 -0
- package/lib/validators/github.test.js +61 -0
- package/lib/validators/index.js +15 -0
- package/lib/validators/postgres.js +525 -0
- package/package.json +98 -13
- package/scripts/benchmark-memory.js +433 -0
- package/scripts/check-secrets.sh +12 -0
- package/scripts/fetch-todos.mjs +148 -0
- package/scripts/graceful-shutdown.sh +156 -0
- package/scripts/install-onyx-hooks.js +373 -0
- package/scripts/install.js +119 -18
- package/scripts/redis-monitor.js +284 -0
- package/scripts/redis-setup.js +412 -0
- package/scripts/test-memory-retrieval.js +201 -0
- package/scripts/validate-exports.js +68 -0
- package/scripts/validate-package.js +120 -0
- package/scripts/verify-onyx-deployment.js +309 -0
- package/scripts/verify-redis-deployment.js +354 -0
- package/scripts/verify-redis-init.js +219 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration Validator Test Suite
|
|
5
|
+
* Tests the config validator with various scenarios
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
validateRedisUrl,
|
|
10
|
+
validateGitHubToken,
|
|
11
|
+
validateOpenAiKey,
|
|
12
|
+
validateConfig,
|
|
13
|
+
printReport
|
|
14
|
+
} from './config.js';
|
|
15
|
+
import chalk from 'chalk';
|
|
16
|
+
|
|
17
|
+
console.log(chalk.bold.blue('\n🧪 Boss Claude Configuration Validator Tests\n'));
|
|
18
|
+
|
|
19
|
+
// Test 1: Valid Redis URLs
|
|
20
|
+
console.log(chalk.bold('Test 1: Redis URL Validation'));
|
|
21
|
+
console.log('─'.repeat(60));
|
|
22
|
+
|
|
23
|
+
const redisTests = [
|
|
24
|
+
{
|
|
25
|
+
name: 'Valid Redis URL (Railway)',
|
|
26
|
+
url: 'redis://user:password@test.example.com:6379',
|
|
27
|
+
shouldPass: true
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'Valid Redis URL with SSL',
|
|
31
|
+
url: 'rediss://default:password@redis.production.com:6379/0',
|
|
32
|
+
shouldPass: true
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'Valid Redis URL (no auth)',
|
|
36
|
+
url: 'redis://localhost:6379',
|
|
37
|
+
shouldPass: false // Should fail as placeholder
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'Invalid Redis URL (missing host)',
|
|
41
|
+
url: 'redis://:password@:6379',
|
|
42
|
+
shouldPass: false
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'Invalid Redis URL (wrong protocol)',
|
|
46
|
+
url: 'http://localhost:6379',
|
|
47
|
+
shouldPass: false
|
|
48
|
+
}
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
redisTests.forEach(test => {
|
|
52
|
+
const result = validateRedisUrl(test.url);
|
|
53
|
+
const passed = result.valid === test.shouldPass;
|
|
54
|
+
const icon = passed ? chalk.green('✓') : chalk.red('✗');
|
|
55
|
+
console.log(`${icon} ${test.name}`);
|
|
56
|
+
if (!passed) {
|
|
57
|
+
console.log(` Expected: ${test.shouldPass}, Got: ${result.valid}`);
|
|
58
|
+
console.log(` Error: ${result.error}`);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
console.log();
|
|
63
|
+
|
|
64
|
+
// Test 2: GitHub Token Validation
|
|
65
|
+
console.log(chalk.bold('Test 2: GitHub Token Validation'));
|
|
66
|
+
console.log('─'.repeat(60));
|
|
67
|
+
|
|
68
|
+
const githubTests = [
|
|
69
|
+
{
|
|
70
|
+
name: 'Valid classic token',
|
|
71
|
+
token: 'ghp_1234567890abcdefghijklmnopqrstuvwxyz12',
|
|
72
|
+
shouldPass: true
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'Valid fine-grained token',
|
|
76
|
+
token: 'github_pat_11ABCDEFG0123456789abcdefghijklmnopqrstuvwxyz1234567890',
|
|
77
|
+
shouldPass: true
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'Invalid - placeholder',
|
|
81
|
+
token: 'YOUR_TOKEN_HERE',
|
|
82
|
+
shouldPass: false
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'Invalid - too short',
|
|
86
|
+
token: 'ghp_short',
|
|
87
|
+
shouldPass: false
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'Invalid - wrong prefix',
|
|
91
|
+
token: 'abc_1234567890abcdefghijklmnopqrstuvwxyz12',
|
|
92
|
+
shouldPass: false
|
|
93
|
+
}
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
githubTests.forEach(test => {
|
|
97
|
+
const result = validateGitHubToken(test.token);
|
|
98
|
+
const passed = result.valid === test.shouldPass;
|
|
99
|
+
const icon = passed ? chalk.green('✓') : chalk.red('✗');
|
|
100
|
+
console.log(`${icon} ${test.name}`);
|
|
101
|
+
if (!passed) {
|
|
102
|
+
console.log(` Expected: ${test.shouldPass}, Got: ${result.valid}`);
|
|
103
|
+
console.log(` Error: ${result.error}`);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
console.log();
|
|
108
|
+
|
|
109
|
+
// Test 3: OpenAI API Key Validation
|
|
110
|
+
console.log(chalk.bold('Test 3: OpenAI API Key Validation'));
|
|
111
|
+
console.log('─'.repeat(60));
|
|
112
|
+
|
|
113
|
+
const openaiTests = [
|
|
114
|
+
{
|
|
115
|
+
name: 'Valid legacy key',
|
|
116
|
+
key: 'sk-1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJ',
|
|
117
|
+
shouldPass: true
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'Valid project key',
|
|
121
|
+
key: 'sk-proj-1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJ1234567890',
|
|
122
|
+
shouldPass: true
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'Invalid - placeholder',
|
|
126
|
+
key: 'YOUR_OPENAI_KEY',
|
|
127
|
+
shouldPass: false
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'Invalid - wrong prefix',
|
|
131
|
+
key: 'pk-1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJ',
|
|
132
|
+
shouldPass: false
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'Invalid - too short',
|
|
136
|
+
key: 'sk-short',
|
|
137
|
+
shouldPass: false
|
|
138
|
+
}
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
openaiTests.forEach(test => {
|
|
142
|
+
const result = validateOpenAiKey(test.key);
|
|
143
|
+
const passed = result.valid === test.shouldPass;
|
|
144
|
+
const icon = passed ? chalk.green('✓') : chalk.red('✗');
|
|
145
|
+
console.log(`${icon} ${test.name}`);
|
|
146
|
+
if (!passed) {
|
|
147
|
+
console.log(` Expected: ${test.shouldPass}, Got: ${result.valid}`);
|
|
148
|
+
console.log(` Error: ${result.error}`);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
console.log();
|
|
153
|
+
|
|
154
|
+
// Test 4: Full Configuration Validation
|
|
155
|
+
console.log(chalk.bold('Test 4: Full Configuration Validation'));
|
|
156
|
+
console.log('─'.repeat(60));
|
|
157
|
+
console.log(chalk.dim('Testing actual ~/.boss-claude/.env file...\n'));
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const report = await validateConfig({ includeOptional: true });
|
|
161
|
+
printReport(report);
|
|
162
|
+
|
|
163
|
+
if (report.valid) {
|
|
164
|
+
console.log(chalk.green.bold('\n✅ All tests passed!'));
|
|
165
|
+
} else {
|
|
166
|
+
console.log(chalk.yellow.bold('\n⚠️ Some tests failed (see report above)'));
|
|
167
|
+
}
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error(chalk.red('\n❌ Error running full validation:'), error.message);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log('\n' + '═'.repeat(60));
|
|
174
|
+
console.log(chalk.bold.blue('🎯 Test Suite Complete'));
|
|
175
|
+
console.log('═'.repeat(60) + '\n');
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { Octokit } from '@octokit/rest';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GitHub Token Validator
|
|
5
|
+
* Tests if a GitHub token has the required permissions for Boss Claude operations
|
|
6
|
+
*
|
|
7
|
+
* Required Permissions:
|
|
8
|
+
* - Issues: Read/Write (repo scope or issues scope)
|
|
9
|
+
* - Contents: Read/Write (repo scope or contents scope)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validates GitHub token permissions
|
|
14
|
+
* @param {string} token - GitHub personal access token
|
|
15
|
+
* @param {Object} options - Validation options
|
|
16
|
+
* @param {boolean} options.strict - If true, requires exact scopes. If false, allows broader scopes like 'repo'
|
|
17
|
+
* @returns {Promise<Object>} Validation result
|
|
18
|
+
*/
|
|
19
|
+
export async function validateGitHubToken(token, options = { strict: false }) {
|
|
20
|
+
if (!token) {
|
|
21
|
+
return {
|
|
22
|
+
valid: false,
|
|
23
|
+
error: 'No GitHub token provided',
|
|
24
|
+
missingPermissions: ['issues', 'contents'],
|
|
25
|
+
suggestion: 'Provide a GitHub personal access token'
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const octokit = new Octokit({ auth: token });
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Get token information
|
|
33
|
+
const { data: user, headers } = await octokit.rest.users.getAuthenticated();
|
|
34
|
+
|
|
35
|
+
// Parse X-OAuth-Scopes header to get token scopes
|
|
36
|
+
const scopeHeader = headers['x-oauth-scopes'];
|
|
37
|
+
const scopes = scopeHeader ? scopeHeader.split(',').map(s => s.trim()) : [];
|
|
38
|
+
|
|
39
|
+
// Check for required permissions
|
|
40
|
+
const requiredPermissions = {
|
|
41
|
+
issues: checkIssuesPermission(scopes, options.strict),
|
|
42
|
+
contents: checkContentsPermission(scopes, options.strict)
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const hasAllPermissions = requiredPermissions.issues && requiredPermissions.contents;
|
|
46
|
+
const missingPermissions = [];
|
|
47
|
+
|
|
48
|
+
if (!requiredPermissions.issues) {
|
|
49
|
+
missingPermissions.push('issues');
|
|
50
|
+
}
|
|
51
|
+
if (!requiredPermissions.contents) {
|
|
52
|
+
missingPermissions.push('contents');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (hasAllPermissions) {
|
|
56
|
+
return {
|
|
57
|
+
valid: true,
|
|
58
|
+
user: user.login,
|
|
59
|
+
scopes: scopes,
|
|
60
|
+
permissions: {
|
|
61
|
+
issues: 'read/write',
|
|
62
|
+
contents: 'read/write'
|
|
63
|
+
},
|
|
64
|
+
message: `✅ GitHub token valid for user: ${user.login}`
|
|
65
|
+
};
|
|
66
|
+
} else {
|
|
67
|
+
return {
|
|
68
|
+
valid: false,
|
|
69
|
+
user: user.login,
|
|
70
|
+
scopes: scopes,
|
|
71
|
+
error: 'Token is missing required permissions',
|
|
72
|
+
missingPermissions: missingPermissions,
|
|
73
|
+
currentPermissions: {
|
|
74
|
+
issues: requiredPermissions.issues ? 'read/write' : 'none or read-only',
|
|
75
|
+
contents: requiredPermissions.contents ? 'read/write' : 'none or read-only'
|
|
76
|
+
},
|
|
77
|
+
suggestion: generatePermissionSuggestion(missingPermissions, scopes)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
} catch (error) {
|
|
82
|
+
// Handle specific error cases
|
|
83
|
+
if (error.status === 401) {
|
|
84
|
+
return {
|
|
85
|
+
valid: false,
|
|
86
|
+
error: 'Invalid or expired GitHub token',
|
|
87
|
+
details: error.message,
|
|
88
|
+
suggestion: 'Generate a new personal access token at https://github.com/settings/tokens'
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (error.status === 403) {
|
|
93
|
+
return {
|
|
94
|
+
valid: false,
|
|
95
|
+
error: 'GitHub API rate limit exceeded or token lacks basic access',
|
|
96
|
+
details: error.message,
|
|
97
|
+
suggestion: 'Wait for rate limit reset or check token permissions'
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
valid: false,
|
|
103
|
+
error: 'Failed to validate GitHub token',
|
|
104
|
+
details: error.message,
|
|
105
|
+
suggestion: 'Check your network connection and token validity'
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if token has issues read/write permission
|
|
112
|
+
* @param {Array<string>} scopes - Token scopes
|
|
113
|
+
* @param {boolean} strict - Strict mode flag
|
|
114
|
+
* @returns {boolean} True if has permission
|
|
115
|
+
*/
|
|
116
|
+
function checkIssuesPermission(scopes, strict) {
|
|
117
|
+
// 'repo' scope includes full access to repositories (issues, contents, etc.)
|
|
118
|
+
// 'public_repo' includes access to public repositories
|
|
119
|
+
// Fine-grained tokens might have specific 'issues' scope
|
|
120
|
+
|
|
121
|
+
if (strict) {
|
|
122
|
+
// In strict mode, require explicit issues scope or repo scope
|
|
123
|
+
return scopes.includes('repo') || scopes.includes('issues');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// In non-strict mode, allow broader scopes
|
|
127
|
+
return scopes.includes('repo') ||
|
|
128
|
+
scopes.includes('public_repo') ||
|
|
129
|
+
scopes.includes('issues');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check if token has contents read/write permission
|
|
134
|
+
* @param {Array<string>} scopes - Token scopes
|
|
135
|
+
* @param {boolean} strict - Strict mode flag
|
|
136
|
+
* @returns {boolean} True if has permission
|
|
137
|
+
*/
|
|
138
|
+
function checkContentsPermission(scopes, strict) {
|
|
139
|
+
// 'repo' scope includes full access to repositories
|
|
140
|
+
// 'public_repo' includes access to public repositories
|
|
141
|
+
// Fine-grained tokens might have specific 'contents' scope
|
|
142
|
+
|
|
143
|
+
if (strict) {
|
|
144
|
+
// In strict mode, require explicit contents scope or repo scope
|
|
145
|
+
return scopes.includes('repo') || scopes.includes('contents');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// In non-strict mode, allow broader scopes
|
|
149
|
+
return scopes.includes('repo') ||
|
|
150
|
+
scopes.includes('public_repo');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Generate helpful permission suggestion based on missing permissions
|
|
155
|
+
* @param {Array<string>} missing - Missing permission names
|
|
156
|
+
* @param {Array<string>} current - Current scopes
|
|
157
|
+
* @returns {string} Helpful suggestion
|
|
158
|
+
*/
|
|
159
|
+
function generatePermissionSuggestion(missing, current) {
|
|
160
|
+
const suggestions = [];
|
|
161
|
+
|
|
162
|
+
if (missing.length === 0) {
|
|
163
|
+
return 'Token has all required permissions';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
suggestions.push('To fix this, create a new GitHub token with the following scopes:');
|
|
167
|
+
|
|
168
|
+
if (current.includes('repo')) {
|
|
169
|
+
suggestions.push(' - Your token already has "repo" scope, but it may be read-only');
|
|
170
|
+
suggestions.push(' - Ensure the token has write access to repositories');
|
|
171
|
+
} else {
|
|
172
|
+
suggestions.push(' - Add "repo" scope for full repository access (recommended)');
|
|
173
|
+
suggestions.push(' OR');
|
|
174
|
+
|
|
175
|
+
if (missing.includes('issues')) {
|
|
176
|
+
suggestions.push(' - Add "issues" scope for issues read/write access');
|
|
177
|
+
}
|
|
178
|
+
if (missing.includes('contents')) {
|
|
179
|
+
suggestions.push(' - Add "contents" scope for repository contents read/write access');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
suggestions.push('');
|
|
184
|
+
suggestions.push('Create a new token at: https://github.com/settings/tokens/new');
|
|
185
|
+
suggestions.push('Or use fine-grained tokens at: https://github.com/settings/personal-access-tokens/new');
|
|
186
|
+
|
|
187
|
+
return suggestions.join('\n');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Test token against a specific repository
|
|
192
|
+
* @param {string} token - GitHub token
|
|
193
|
+
* @param {string} owner - Repository owner
|
|
194
|
+
* @param {string} repo - Repository name
|
|
195
|
+
* @returns {Promise<Object>} Test result
|
|
196
|
+
*/
|
|
197
|
+
export async function testTokenOnRepository(token, owner, repo) {
|
|
198
|
+
const octokit = new Octokit({ auth: token });
|
|
199
|
+
|
|
200
|
+
const tests = {
|
|
201
|
+
readIssues: false,
|
|
202
|
+
writeIssues: false,
|
|
203
|
+
readContents: false,
|
|
204
|
+
writeContents: false
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const errors = [];
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
// Test 1: Read issues
|
|
211
|
+
try {
|
|
212
|
+
await octokit.rest.issues.listForRepo({ owner, repo, per_page: 1 });
|
|
213
|
+
tests.readIssues = true;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
errors.push(`Read Issues: ${error.message}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Test 2: Create a test issue (we'll close it immediately)
|
|
219
|
+
try {
|
|
220
|
+
const { data: issue } = await octokit.rest.issues.create({
|
|
221
|
+
owner,
|
|
222
|
+
repo,
|
|
223
|
+
title: '[Boss Claude] Token validation test',
|
|
224
|
+
body: 'This is an automated test. Closing immediately.',
|
|
225
|
+
labels: ['bot', 'test']
|
|
226
|
+
});
|
|
227
|
+
tests.writeIssues = true;
|
|
228
|
+
|
|
229
|
+
// Close the test issue
|
|
230
|
+
await octokit.rest.issues.update({
|
|
231
|
+
owner,
|
|
232
|
+
repo,
|
|
233
|
+
issue_number: issue.number,
|
|
234
|
+
state: 'closed'
|
|
235
|
+
});
|
|
236
|
+
} catch (error) {
|
|
237
|
+
errors.push(`Write Issues: ${error.message}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Test 3: Read contents
|
|
241
|
+
try {
|
|
242
|
+
await octokit.rest.repos.getContent({ owner, repo, path: 'README.md' });
|
|
243
|
+
tests.readContents = true;
|
|
244
|
+
} catch (error) {
|
|
245
|
+
errors.push(`Read Contents: ${error.message}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Test 4: Write contents (get latest commit to test write access)
|
|
249
|
+
try {
|
|
250
|
+
await octokit.rest.repos.listCommits({ owner, repo, per_page: 1 });
|
|
251
|
+
tests.writeContents = true;
|
|
252
|
+
} catch (error) {
|
|
253
|
+
errors.push(`Write Contents: ${error.message}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const allTestsPassed = Object.values(tests).every(t => t === true);
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
success: allTestsPassed,
|
|
260
|
+
repository: `${owner}/${repo}`,
|
|
261
|
+
tests,
|
|
262
|
+
errors: errors.length > 0 ? errors : null,
|
|
263
|
+
message: allTestsPassed
|
|
264
|
+
? `✅ All permissions verified on ${owner}/${repo}`
|
|
265
|
+
: `❌ Some permissions are missing on ${owner}/${repo}`
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
} catch (error) {
|
|
269
|
+
return {
|
|
270
|
+
success: false,
|
|
271
|
+
repository: `${owner}/${repo}`,
|
|
272
|
+
error: `Failed to test repository: ${error.message}`,
|
|
273
|
+
suggestion: 'Ensure the repository exists and the token has access to it'
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Validate and display results in a user-friendly format
|
|
280
|
+
* @param {string} token - GitHub token
|
|
281
|
+
* @param {Object} options - Validation options
|
|
282
|
+
* @returns {Promise<boolean>} True if valid
|
|
283
|
+
*/
|
|
284
|
+
export async function validateAndDisplay(token, options = {}) {
|
|
285
|
+
const result = await validateGitHubToken(token, options);
|
|
286
|
+
|
|
287
|
+
if (result.valid) {
|
|
288
|
+
console.log('\n✅ GitHub Token Validation: PASSED');
|
|
289
|
+
console.log(`User: ${result.user}`);
|
|
290
|
+
console.log(`Scopes: ${result.scopes.join(', ')}`);
|
|
291
|
+
console.log(`Permissions: Issues (R/W), Contents (R/W)`);
|
|
292
|
+
} else {
|
|
293
|
+
console.log('\n❌ GitHub Token Validation: FAILED');
|
|
294
|
+
console.log(`Error: ${result.error}`);
|
|
295
|
+
if (result.missingPermissions) {
|
|
296
|
+
console.log(`Missing: ${result.missingPermissions.join(', ')}`);
|
|
297
|
+
}
|
|
298
|
+
if (result.suggestion) {
|
|
299
|
+
console.log(`\n${result.suggestion}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return result.valid;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export default {
|
|
307
|
+
validateGitHubToken,
|
|
308
|
+
testTokenOnRepository,
|
|
309
|
+
validateAndDisplay
|
|
310
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Token Validator Test
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* node lib/validators/github.test.js <github-token>
|
|
6
|
+
* node lib/validators/github.test.js <github-token> <owner> <repo>
|
|
7
|
+
*
|
|
8
|
+
* Examples:
|
|
9
|
+
* node lib/validators/github.test.js ghp_abc123...
|
|
10
|
+
* node lib/validators/github.test.js ghp_abc123... cpretzinger boss-claude
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { validateGitHubToken, testTokenOnRepository, validateAndDisplay } from './github.js';
|
|
14
|
+
|
|
15
|
+
async function main() {
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
|
|
18
|
+
if (args.length === 0) {
|
|
19
|
+
console.log('Usage: node github.test.js <github-token> [owner] [repo]');
|
|
20
|
+
console.log('');
|
|
21
|
+
console.log('Examples:');
|
|
22
|
+
console.log(' node github.test.js ghp_abc123...');
|
|
23
|
+
console.log(' node github.test.js ghp_abc123... cpretzinger boss-claude');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const token = args[0];
|
|
28
|
+
const owner = args[1];
|
|
29
|
+
const repo = args[2];
|
|
30
|
+
|
|
31
|
+
console.log('🔍 Validating GitHub Token...\n');
|
|
32
|
+
|
|
33
|
+
// Basic validation
|
|
34
|
+
const isValid = await validateAndDisplay(token);
|
|
35
|
+
|
|
36
|
+
// If repository specified, run repository-specific tests
|
|
37
|
+
if (owner && repo && isValid) {
|
|
38
|
+
console.log('\n🔍 Testing Token on Repository...\n');
|
|
39
|
+
const repoTest = await testTokenOnRepository(token, owner, repo);
|
|
40
|
+
|
|
41
|
+
console.log(`Repository: ${repoTest.repository}`);
|
|
42
|
+
console.log(`Status: ${repoTest.success ? '✅ PASSED' : '❌ FAILED'}`);
|
|
43
|
+
console.log('\nPermission Tests:');
|
|
44
|
+
console.log(` Read Issues: ${repoTest.tests.readIssues ? '✅' : '❌'}`);
|
|
45
|
+
console.log(` Write Issues: ${repoTest.tests.writeIssues ? '✅' : '❌'}`);
|
|
46
|
+
console.log(` Read Contents: ${repoTest.tests.readContents ? '✅' : '❌'}`);
|
|
47
|
+
console.log(` Write Contents: ${repoTest.tests.writeContents ? '✅' : '❌'}`);
|
|
48
|
+
|
|
49
|
+
if (repoTest.errors) {
|
|
50
|
+
console.log('\nErrors:');
|
|
51
|
+
repoTest.errors.forEach(err => console.log(` - ${err}`));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
process.exit(isValid ? 0 : 1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
main().catch(error => {
|
|
59
|
+
console.error('Error:', error.message);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boss Claude Validators
|
|
3
|
+
*
|
|
4
|
+
* Validation utilities for external service credentials and permissions
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export * from './github.js';
|
|
8
|
+
export * from './postgres.js';
|
|
9
|
+
export * from './config.js';
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
github: () => import('./github.js'),
|
|
13
|
+
postgres: () => import('./postgres.js'),
|
|
14
|
+
config: () => import('./config.js')
|
|
15
|
+
};
|