@jaguilar87/gaia-ops 1.3.4 → 1.3.6
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/CHANGELOG.md +43 -0
- package/bin/gaia-init.js +247 -59
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.3.6] - 2025-11-10
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Installer:** Skip questions when project context already has the answers
|
|
14
|
+
- **Smart Detection:** Only ask what's missing or needs confirmation (paths)
|
|
15
|
+
- **User Experience:** Show config summary when context is loaded
|
|
16
|
+
- **Directory Creation:** Auto-create missing directories without prompting
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- When project context loads successfully, only asks to confirm/adjust paths
|
|
20
|
+
- Cloud provider, credentials, region, and cluster name auto-applied from context
|
|
21
|
+
- Clearer feedback showing what was loaded from project context
|
|
22
|
+
- Missing directories (gitops, terraform, app-services) now created automatically
|
|
23
|
+
|
|
24
|
+
### Improved
|
|
25
|
+
- Eliminates ALL redundant questions when context exists
|
|
26
|
+
- Better UX: "Here's what we loaded, just confirm the paths"
|
|
27
|
+
- Faster setup for teams with complete project contexts
|
|
28
|
+
- No interruptions for directory creation confirmations
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## [1.3.5] - 2025-11-10
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- **Smart Installer Flow:** Project context repo now asked FIRST, with auto-population of all config
|
|
36
|
+
- **Input Sanitization:** Handles "git clone <url>" pastes automatically (extracts just URL)
|
|
37
|
+
- **Auto-Configuration:** Parses project-context.json and pre-fills all wizard questions
|
|
38
|
+
- **Better Error Messages:** Clear troubleshooting tips for git clone failures (SSH keys, access, URL)
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
- **Wizard Question Order:** Project context moved from last to first question
|
|
42
|
+
- **User Experience:** Reduced manual input when project context exists
|
|
43
|
+
- **Clone Strategy:** Validates project context early, then sets up in final location
|
|
44
|
+
- **Error Handling:** Installation continues even if project context clone fails
|
|
45
|
+
|
|
46
|
+
### Improved
|
|
47
|
+
- Eliminates typos and configuration errors by pre-filling from existing context
|
|
48
|
+
- Saves time for users with existing project-context repos
|
|
49
|
+
- Better guidance when git operations fail
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
10
53
|
## [1.3.4] - 2025-11-10
|
|
11
54
|
|
|
12
55
|
### Fixed
|
package/bin/gaia-init.js
CHANGED
|
@@ -254,6 +254,81 @@ function validateConfiguration(config, nonInteractive) {
|
|
|
254
254
|
// Interactive prompts
|
|
255
255
|
// ============================================================================
|
|
256
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Sanitize git repo URL input
|
|
259
|
+
* Handles cases where user pastes "git clone <url>" instead of just "<url>"
|
|
260
|
+
*/
|
|
261
|
+
function sanitizeGitUrl(input) {
|
|
262
|
+
if (!input || typeof input !== 'string') return '';
|
|
263
|
+
|
|
264
|
+
// Trim whitespace
|
|
265
|
+
let sanitized = input.trim();
|
|
266
|
+
|
|
267
|
+
// Remove "git clone" prefix if present
|
|
268
|
+
if (sanitized.toLowerCase().startsWith('git clone ')) {
|
|
269
|
+
sanitized = sanitized.substring('git clone '.length).trim();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Remove quotes if present
|
|
273
|
+
sanitized = sanitized.replace(/^["']|["']$/g, '');
|
|
274
|
+
|
|
275
|
+
return sanitized;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Try to clone project context repo early and parse config
|
|
280
|
+
* Returns parsed config or null if clone fails
|
|
281
|
+
*/
|
|
282
|
+
async function tryCloneProjectContext(repoUrl) {
|
|
283
|
+
if (!repoUrl || repoUrl.trim() === '') {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const sanitizedUrl = sanitizeGitUrl(repoUrl);
|
|
288
|
+
|
|
289
|
+
const spinner = ora('Cloning project context repository...').start();
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const tempDir = join(CWD, '.claude-temp-context');
|
|
293
|
+
|
|
294
|
+
// Remove temp dir if exists
|
|
295
|
+
if (existsSync(tempDir)) {
|
|
296
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Clone to temp directory
|
|
300
|
+
await execAsync(`git clone ${sanitizedUrl} ${tempDir}`, { timeout: 30000 });
|
|
301
|
+
|
|
302
|
+
// Try to read project-context.json
|
|
303
|
+
const contextPath = join(tempDir, 'project-context.json');
|
|
304
|
+
|
|
305
|
+
if (existsSync(contextPath)) {
|
|
306
|
+
const contextData = JSON.parse(await fs.readFile(contextPath, 'utf-8'));
|
|
307
|
+
|
|
308
|
+
// Clean up temp dir
|
|
309
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
310
|
+
|
|
311
|
+
spinner.succeed('Project context loaded successfully');
|
|
312
|
+
console.log(chalk.green(' ✓ Auto-populated configuration from project context\n'));
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
contextData,
|
|
316
|
+
repoUrl: sanitizedUrl
|
|
317
|
+
};
|
|
318
|
+
} else {
|
|
319
|
+
// No project-context.json found
|
|
320
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
321
|
+
spinner.warn('Repository cloned but no project-context.json found');
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
} catch (error) {
|
|
325
|
+
spinner.fail('Failed to clone project context repository');
|
|
326
|
+
console.log(chalk.yellow(`\n⚠️ Error: ${error.message}`));
|
|
327
|
+
console.log(chalk.gray(' Continuing with manual configuration...\n'));
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
257
332
|
/**
|
|
258
333
|
* Present interactive wizard to user
|
|
259
334
|
*/
|
|
@@ -276,36 +351,97 @@ async function runInteractiveWizard(detected) {
|
|
|
276
351
|
|
|
277
352
|
console.log(chalk.gray('This wizard will set up the Gaia-Ops agent system for your project.\n'));
|
|
278
353
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
console.log(chalk.
|
|
283
|
-
console.log(chalk.gray('
|
|
354
|
+
// =========================================================================
|
|
355
|
+
// STEP 1: Ask for project context repo first (if available)
|
|
356
|
+
// =========================================================================
|
|
357
|
+
console.log(chalk.yellow('🔗 Project Context (Optional but Recommended)'));
|
|
358
|
+
console.log(chalk.gray('If you have a project context repo, we can auto-populate your configuration.\n'));
|
|
359
|
+
|
|
360
|
+
const contextQuestion = await prompts({
|
|
361
|
+
type: 'text',
|
|
362
|
+
name: 'projectContextRepo',
|
|
363
|
+
message: '📦 Project context Git repo (e.g., git@bitbucket.org:org/context.git):',
|
|
364
|
+
initial: ''
|
|
365
|
+
}, {
|
|
366
|
+
onCancel: () => {
|
|
367
|
+
console.log(chalk.yellow('\n⚠️ Installation cancelled by user\n'));
|
|
368
|
+
process.exit(0);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// Try to clone and parse project context
|
|
373
|
+
let projectContext = null;
|
|
374
|
+
if (contextQuestion.projectContextRepo) {
|
|
375
|
+
projectContext = await tryCloneProjectContext(contextQuestion.projectContextRepo);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Extract defaults from project context if available
|
|
379
|
+
const defaults = projectContext ? {
|
|
380
|
+
gitops: projectContext.contextData.paths?.gitops || detected.gitops || './gitops',
|
|
381
|
+
terraform: projectContext.contextData.paths?.terraform || detected.terraform || './terraform',
|
|
382
|
+
appServices: projectContext.contextData.paths?.app_services || detected.appServices || './app-services',
|
|
383
|
+
cloudProvider: projectContext.contextData.sections?.project_details?.cloud_provider || 'gcp',
|
|
384
|
+
gcpProjectId: projectContext.contextData.sections?.project_details?.project_id || projectContext.contextData.metadata?.project_id || '',
|
|
385
|
+
awsAccountId: projectContext.contextData.sections?.project_details?.aws_account || projectContext.contextData.metadata?.aws_account || '',
|
|
386
|
+
region: projectContext.contextData.sections?.project_details?.region || projectContext.contextData.metadata?.primary_region || 'us-central1',
|
|
387
|
+
clusterName: projectContext.contextData.sections?.project_details?.cluster_name || '',
|
|
388
|
+
repoUrl: projectContext?.repoUrl || contextQuestion.projectContextRepo
|
|
389
|
+
} : {
|
|
390
|
+
gitops: detected.gitops || './gitops',
|
|
391
|
+
terraform: detected.terraform || './terraform',
|
|
392
|
+
appServices: detected.appServices || './app-services',
|
|
393
|
+
cloudProvider: 'gcp',
|
|
394
|
+
gcpProjectId: '',
|
|
395
|
+
awsAccountId: '',
|
|
396
|
+
region: 'us-central1',
|
|
397
|
+
clusterName: '',
|
|
398
|
+
repoUrl: contextQuestion.projectContextRepo
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
// =========================================================================
|
|
402
|
+
// STEP 2: Ask remaining questions (with smart defaults from context)
|
|
403
|
+
// =========================================================================
|
|
404
|
+
|
|
405
|
+
// If we have project context, show a summary and only ask to confirm paths
|
|
406
|
+
if (projectContext) {
|
|
407
|
+
console.log(chalk.green('\n✅ Configuration loaded from project context:'));
|
|
408
|
+
console.log(chalk.gray(` • Cloud: ${defaults.cloudProvider.toUpperCase()}`));
|
|
409
|
+
if (defaults.gcpProjectId) console.log(chalk.gray(` • GCP Project: ${defaults.gcpProjectId}`));
|
|
410
|
+
if (defaults.awsAccountId) console.log(chalk.gray(` • AWS Account: ${defaults.awsAccountId}`));
|
|
411
|
+
console.log(chalk.gray(` • Region: ${defaults.region}`));
|
|
412
|
+
console.log(chalk.gray(` • Cluster: ${defaults.clusterName}`));
|
|
413
|
+
console.log(chalk.yellow('\n📍 Directory Configuration'));
|
|
414
|
+
console.log(chalk.gray('Verify or adjust paths if needed:\n'));
|
|
415
|
+
} else {
|
|
416
|
+
console.log(chalk.yellow('\n📍 Directory Configuration'));
|
|
417
|
+
console.log(chalk.gray('Please provide your project configuration:\n'));
|
|
418
|
+
}
|
|
284
419
|
|
|
285
420
|
const questions = [
|
|
286
421
|
{
|
|
287
422
|
type: 'text',
|
|
288
423
|
name: 'gitops',
|
|
289
424
|
message: '📦 GitOps directory:',
|
|
290
|
-
initial:
|
|
425
|
+
initial: defaults.gitops,
|
|
291
426
|
validate: value => value.trim().length > 0
|
|
292
427
|
},
|
|
293
428
|
{
|
|
294
429
|
type: 'text',
|
|
295
430
|
name: 'terraform',
|
|
296
431
|
message: '🔧 Terraform directory:',
|
|
297
|
-
initial:
|
|
432
|
+
initial: defaults.terraform,
|
|
298
433
|
validate: value => value.trim().length > 0
|
|
299
434
|
},
|
|
300
435
|
{
|
|
301
436
|
type: 'text',
|
|
302
437
|
name: 'appServices',
|
|
303
438
|
message: '🚀 App Services directory:',
|
|
304
|
-
initial:
|
|
439
|
+
initial: defaults.appServices,
|
|
305
440
|
validate: value => value.trim().length > 0
|
|
306
441
|
},
|
|
442
|
+
// Only ask cloud provider if not loaded from context
|
|
307
443
|
{
|
|
308
|
-
type: 'select',
|
|
444
|
+
type: projectContext ? null : 'select',
|
|
309
445
|
name: 'cloudProvider',
|
|
310
446
|
message: '☁️ Cloud provider:',
|
|
311
447
|
choices: [
|
|
@@ -313,31 +449,48 @@ async function runInteractiveWizard(detected) {
|
|
|
313
449
|
{ title: 'AWS (Amazon Web Services)', value: 'aws' },
|
|
314
450
|
{ title: 'Multi-cloud (AWS + GCP)', value: 'multi-cloud' }
|
|
315
451
|
],
|
|
316
|
-
initial: 0
|
|
452
|
+
initial: defaults.cloudProvider === 'gcp' ? 0 : defaults.cloudProvider === 'aws' ? 1 : 2
|
|
317
453
|
},
|
|
454
|
+
// Only ask GCP Project ID if not loaded from context
|
|
318
455
|
{
|
|
319
|
-
type: (prev, values) =>
|
|
456
|
+
type: (prev, values) => {
|
|
457
|
+
const provider = values.cloudProvider || defaults.cloudProvider;
|
|
458
|
+
const needsGcp = ['gcp', 'multi-cloud'].includes(provider);
|
|
459
|
+
const hasValue = projectContext && defaults.gcpProjectId;
|
|
460
|
+
return (needsGcp && !hasValue) ? 'text' : null;
|
|
461
|
+
},
|
|
320
462
|
name: 'gcpProjectId',
|
|
321
463
|
message: '🌐 GCP Project ID (e.g., aaxis-rnd-non-prod):',
|
|
464
|
+
initial: defaults.gcpProjectId,
|
|
322
465
|
validate: value => value.trim().length > 0
|
|
323
466
|
},
|
|
467
|
+
// Only ask AWS Account if not loaded from context
|
|
324
468
|
{
|
|
325
|
-
type: (prev, values) =>
|
|
469
|
+
type: (prev, values) => {
|
|
470
|
+
const provider = values.cloudProvider || defaults.cloudProvider;
|
|
471
|
+
const needsAws = ['aws', 'multi-cloud'].includes(provider);
|
|
472
|
+
const hasValue = projectContext && defaults.awsAccountId;
|
|
473
|
+
return (needsAws && !hasValue) ? 'text' : null;
|
|
474
|
+
},
|
|
326
475
|
name: 'awsAccountId',
|
|
327
476
|
message: '🌐 AWS Account ID (e.g., 929914624686):',
|
|
477
|
+
initial: defaults.awsAccountId,
|
|
328
478
|
validate: value => value.trim().length > 0
|
|
329
479
|
},
|
|
480
|
+
// Only ask region if not loaded from context
|
|
330
481
|
{
|
|
331
|
-
type: 'text',
|
|
482
|
+
type: projectContext && defaults.region ? null : 'text',
|
|
332
483
|
name: 'region',
|
|
333
484
|
message: '🌍 Primary Region (e.g., us-central1 for GCP, us-east-1 for AWS):',
|
|
334
|
-
initial:
|
|
485
|
+
initial: defaults.region,
|
|
335
486
|
validate: value => value.trim().length > 0
|
|
336
487
|
},
|
|
488
|
+
// Only ask cluster name if not loaded from context
|
|
337
489
|
{
|
|
338
|
-
type: 'text',
|
|
490
|
+
type: projectContext && defaults.clusterName ? null : 'text',
|
|
339
491
|
name: 'clusterName',
|
|
340
492
|
message: '☸️ Cluster Name (e.g., rnd-gke-nonprod or digital-eks-prod):',
|
|
493
|
+
initial: defaults.clusterName,
|
|
341
494
|
validate: value => value.trim().length > 0
|
|
342
495
|
},
|
|
343
496
|
{
|
|
@@ -345,12 +498,6 @@ async function runInteractiveWizard(detected) {
|
|
|
345
498
|
name: 'installClaudeCode',
|
|
346
499
|
message: '📥 Install Claude Code if not present?',
|
|
347
500
|
initial: true
|
|
348
|
-
},
|
|
349
|
-
{
|
|
350
|
-
type: 'text',
|
|
351
|
-
name: 'projectContextRepo',
|
|
352
|
-
message: '🔗 Project context Git repo (optional, e.g., git@bitbucket.org:org/context.git):',
|
|
353
|
-
initial: ''
|
|
354
501
|
}
|
|
355
502
|
];
|
|
356
503
|
|
|
@@ -361,13 +508,21 @@ async function runInteractiveWizard(detected) {
|
|
|
361
508
|
}
|
|
362
509
|
});
|
|
363
510
|
|
|
364
|
-
//
|
|
365
|
-
|
|
511
|
+
// Merge responses with defaults (for values skipped because they were in context)
|
|
512
|
+
const finalConfig = {
|
|
513
|
+
...defaults,
|
|
514
|
+
...responses,
|
|
515
|
+
projectContextRepo: defaults.repoUrl,
|
|
516
|
+
projectContextAlreadyCloned: !!projectContext
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
// Verify critical responses are present
|
|
520
|
+
if (!finalConfig.cloudProvider || !finalConfig.clusterName) {
|
|
366
521
|
console.log(chalk.yellow('\n⚠️ Installation cancelled or incomplete\n'));
|
|
367
522
|
process.exit(0);
|
|
368
523
|
}
|
|
369
524
|
|
|
370
|
-
return
|
|
525
|
+
return finalConfig;
|
|
371
526
|
}
|
|
372
527
|
|
|
373
528
|
// ============================================================================
|
|
@@ -507,9 +662,9 @@ async function generateAgentsMd() {
|
|
|
507
662
|
|
|
508
663
|
/**
|
|
509
664
|
* Validate and setup project paths (gitops, terraform, app-services)
|
|
510
|
-
* Creates directories if they don't exist
|
|
665
|
+
* Creates directories automatically if they don't exist
|
|
511
666
|
*/
|
|
512
|
-
async function validateAndSetupProjectPaths(config
|
|
667
|
+
async function validateAndSetupProjectPaths(config) {
|
|
513
668
|
console.log(chalk.cyan('\n📁 Setting up project directories...\n'));
|
|
514
669
|
|
|
515
670
|
const paths = {
|
|
@@ -527,30 +682,13 @@ async function validateAndSetupProjectPaths(config, nonInteractive) {
|
|
|
527
682
|
continue;
|
|
528
683
|
}
|
|
529
684
|
|
|
530
|
-
//
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
if (!nonInteractive) {
|
|
534
|
-
// Ask user in interactive mode
|
|
535
|
-
const response = await prompts({
|
|
536
|
-
type: 'confirm',
|
|
537
|
-
name: 'create',
|
|
538
|
-
message: `Directory ${chalk.yellow(userPath)} doesn't exist. Create it?`,
|
|
539
|
-
initial: true
|
|
540
|
-
});
|
|
541
|
-
shouldCreate = response.create;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
if (shouldCreate) {
|
|
545
|
-
await fs.mkdir(absPath, { recursive: true });
|
|
546
|
-
console.log(chalk.green(` ✓ ${name}: ${userPath} (created)`));
|
|
547
|
-
} else {
|
|
548
|
-
console.log(chalk.yellow(` ⚠ ${name}: ${userPath} (skipped - agents may create it later if needed)`));
|
|
549
|
-
}
|
|
685
|
+
// Create directory automatically
|
|
686
|
+
await fs.mkdir(absPath, { recursive: true });
|
|
687
|
+
console.log(chalk.green(` ✓ ${name}: ${userPath} (created)`));
|
|
550
688
|
|
|
551
689
|
// Warn about absolute paths (portability concern)
|
|
552
690
|
if (isAbsolute(userPath)) {
|
|
553
|
-
console.log(chalk.yellow(`
|
|
691
|
+
console.log(chalk.yellow(` ⚠ Note: Absolute path may not work on other machines`));
|
|
554
692
|
}
|
|
555
693
|
}
|
|
556
694
|
|
|
@@ -698,13 +836,58 @@ async function installClaudeAgentsPackage() {
|
|
|
698
836
|
|
|
699
837
|
/**
|
|
700
838
|
* Clone project context repository (optional)
|
|
839
|
+
* If already cloned during wizard, skip clone and just set it up in final location
|
|
701
840
|
*/
|
|
702
|
-
async function cloneProjectContextRepo(repoUrl) {
|
|
841
|
+
async function cloneProjectContextRepo(repoUrl, alreadyCloned = false) {
|
|
703
842
|
if (!repoUrl || repoUrl.trim() === '') {
|
|
704
843
|
console.log(chalk.gray('\n✓ Skipping project context repo clone (not provided)\n'));
|
|
705
844
|
return;
|
|
706
845
|
}
|
|
707
846
|
|
|
847
|
+
const sanitizedUrl = sanitizeGitUrl(repoUrl);
|
|
848
|
+
|
|
849
|
+
if (alreadyCloned) {
|
|
850
|
+
// Context was already cloned during wizard, just re-clone to final location
|
|
851
|
+
const spinner = ora('Setting up project context...').start();
|
|
852
|
+
|
|
853
|
+
try {
|
|
854
|
+
const projectContextDir = join(CWD, '.claude', 'project-context');
|
|
855
|
+
|
|
856
|
+
// Remove the generated project-context.json as it will be replaced by the cloned repo
|
|
857
|
+
const generatedFile = join(projectContextDir, 'project-context.json');
|
|
858
|
+
if (existsSync(generatedFile)) {
|
|
859
|
+
await fs.unlink(generatedFile);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Clone fresh to final location (we already validated it works)
|
|
863
|
+
const tempDir = `${projectContextDir}-temp`;
|
|
864
|
+
await execAsync(`git clone ${sanitizedUrl} ${tempDir}`, { timeout: 30000 });
|
|
865
|
+
|
|
866
|
+
// Move contents from temp to project-context
|
|
867
|
+
const files = await fs.readdir(tempDir);
|
|
868
|
+
for (const file of files) {
|
|
869
|
+
const src = join(tempDir, file);
|
|
870
|
+
const dest = join(projectContextDir, file);
|
|
871
|
+
await fs.rename(src, dest);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Remove temp directory
|
|
875
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
876
|
+
|
|
877
|
+
spinner.succeed('Project context repository configured');
|
|
878
|
+
console.log(chalk.green(` → Location: .claude/project-context/\n`));
|
|
879
|
+
} catch (error) {
|
|
880
|
+
spinner.fail('Failed to setup project context repository');
|
|
881
|
+
console.log(chalk.yellow(`\n⚠️ You can clone it manually with:`));
|
|
882
|
+
console.log(chalk.gray(` cd .claude`));
|
|
883
|
+
console.log(chalk.gray(` rm -rf project-context`));
|
|
884
|
+
console.log(chalk.gray(` git clone ${sanitizedUrl} project-context\n`));
|
|
885
|
+
// Don't throw - allow installation to continue
|
|
886
|
+
}
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Not cloned yet during wizard, try to clone now
|
|
708
891
|
const spinner = ora('Cloning project context repository...').start();
|
|
709
892
|
|
|
710
893
|
try {
|
|
@@ -716,11 +899,11 @@ async function cloneProjectContextRepo(repoUrl) {
|
|
|
716
899
|
await fs.unlink(generatedFile);
|
|
717
900
|
}
|
|
718
901
|
|
|
719
|
-
// Clone repo
|
|
720
|
-
|
|
902
|
+
// Clone repo
|
|
903
|
+
const tempDir = `${projectContextDir}-temp`;
|
|
904
|
+
await execAsync(`git clone ${sanitizedUrl} ${tempDir}`, { timeout: 30000 });
|
|
721
905
|
|
|
722
906
|
// Move contents from temp to project-context
|
|
723
|
-
const tempDir = `${projectContextDir}-temp`;
|
|
724
907
|
const files = await fs.readdir(tempDir);
|
|
725
908
|
for (const file of files) {
|
|
726
909
|
const src = join(tempDir, file);
|
|
@@ -729,17 +912,22 @@ async function cloneProjectContextRepo(repoUrl) {
|
|
|
729
912
|
}
|
|
730
913
|
|
|
731
914
|
// Remove temp directory
|
|
732
|
-
await fs.
|
|
915
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
733
916
|
|
|
734
917
|
spinner.succeed('Project context repository cloned');
|
|
735
|
-
console.log(chalk.green(` → Cloned from: ${
|
|
918
|
+
console.log(chalk.green(` → Cloned from: ${sanitizedUrl}`));
|
|
736
919
|
console.log(chalk.gray(` → Location: .claude/project-context/\n`));
|
|
737
920
|
} catch (error) {
|
|
738
921
|
spinner.fail('Failed to clone project context repository');
|
|
739
|
-
console.log(chalk.yellow(`\n⚠️
|
|
740
|
-
console.log(chalk.gray(
|
|
741
|
-
console.log(chalk.gray(
|
|
742
|
-
console.log(chalk.gray(
|
|
922
|
+
console.log(chalk.yellow(`\n⚠️ Error: ${error.message}`));
|
|
923
|
+
console.log(chalk.gray('\n Common issues:'));
|
|
924
|
+
console.log(chalk.gray(' • Check SSH keys are configured: ssh -T git@bitbucket.org'));
|
|
925
|
+
console.log(chalk.gray(' • Verify repository URL is correct'));
|
|
926
|
+
console.log(chalk.gray(' • Ensure you have access to the repository\n'));
|
|
927
|
+
console.log(chalk.yellow(` You can clone it manually later with:`));
|
|
928
|
+
console.log(chalk.gray(` cd .claude`));
|
|
929
|
+
console.log(chalk.gray(` rm -rf project-context`));
|
|
930
|
+
console.log(chalk.gray(` git clone ${sanitizedUrl} project-context\n`));
|
|
743
931
|
// Don't throw - allow installation to continue
|
|
744
932
|
}
|
|
745
933
|
}
|
|
@@ -798,7 +986,7 @@ async function main() {
|
|
|
798
986
|
await installClaudeAgentsPackage();
|
|
799
987
|
|
|
800
988
|
// Step 5.5: Validate and setup project paths (gitops, terraform, app-services)
|
|
801
|
-
await validateAndSetupProjectPaths(config
|
|
989
|
+
await validateAndSetupProjectPaths(config);
|
|
802
990
|
|
|
803
991
|
// Step 6: Create .claude/ directory with symlinks
|
|
804
992
|
await createClaudeDirectory();
|
|
@@ -814,7 +1002,7 @@ async function main() {
|
|
|
814
1002
|
|
|
815
1003
|
// Step 10: Clone project context repository (optional)
|
|
816
1004
|
if (config.projectContextRepo) {
|
|
817
|
-
await cloneProjectContextRepo(config.projectContextRepo);
|
|
1005
|
+
await cloneProjectContextRepo(config.projectContextRepo, config.projectContextAlreadyCloned);
|
|
818
1006
|
}
|
|
819
1007
|
|
|
820
1008
|
// Success message
|