@jaguilar87/gaia-ops 1.3.4 → 1.3.5
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 +21 -0
- package/bin/gaia-init.js +200 -27
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.3.5] - 2025-11-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Smart Installer Flow:** Project context repo now asked FIRST, with auto-population of all config
|
|
14
|
+
- **Input Sanitization:** Handles "git clone <url>" pastes automatically (extracts just URL)
|
|
15
|
+
- **Auto-Configuration:** Parses project-context.json and pre-fills all wizard questions
|
|
16
|
+
- **Better Error Messages:** Clear troubleshooting tips for git clone failures (SSH keys, access, URL)
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- **Wizard Question Order:** Project context moved from last to first question
|
|
20
|
+
- **User Experience:** Reduced manual input when project context exists
|
|
21
|
+
- **Clone Strategy:** Validates project context early, then sets up in final location
|
|
22
|
+
- **Error Handling:** Installation continues even if project context clone fails
|
|
23
|
+
|
|
24
|
+
### Improved
|
|
25
|
+
- Eliminates typos and configuration errors by pre-filling from existing context
|
|
26
|
+
- Saves time for users with existing project-context repos
|
|
27
|
+
- Better guidance when git operations fail
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
10
31
|
## [1.3.4] - 2025-11-10
|
|
11
32
|
|
|
12
33
|
### 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,32 +351,79 @@ 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
|
+
console.log(chalk.yellow('\n📍 Directory Configuration'));
|
|
405
|
+
console.log(chalk.gray('Verify or adjust the following paths:\n'));
|
|
284
406
|
|
|
285
407
|
const questions = [
|
|
286
408
|
{
|
|
287
409
|
type: 'text',
|
|
288
410
|
name: 'gitops',
|
|
289
411
|
message: '📦 GitOps directory:',
|
|
290
|
-
initial:
|
|
412
|
+
initial: defaults.gitops,
|
|
291
413
|
validate: value => value.trim().length > 0
|
|
292
414
|
},
|
|
293
415
|
{
|
|
294
416
|
type: 'text',
|
|
295
417
|
name: 'terraform',
|
|
296
418
|
message: '🔧 Terraform directory:',
|
|
297
|
-
initial:
|
|
419
|
+
initial: defaults.terraform,
|
|
298
420
|
validate: value => value.trim().length > 0
|
|
299
421
|
},
|
|
300
422
|
{
|
|
301
423
|
type: 'text',
|
|
302
424
|
name: 'appServices',
|
|
303
425
|
message: '🚀 App Services directory:',
|
|
304
|
-
initial:
|
|
426
|
+
initial: defaults.appServices,
|
|
305
427
|
validate: value => value.trim().length > 0
|
|
306
428
|
},
|
|
307
429
|
{
|
|
@@ -313,31 +435,34 @@ async function runInteractiveWizard(detected) {
|
|
|
313
435
|
{ title: 'AWS (Amazon Web Services)', value: 'aws' },
|
|
314
436
|
{ title: 'Multi-cloud (AWS + GCP)', value: 'multi-cloud' }
|
|
315
437
|
],
|
|
316
|
-
initial: 0
|
|
438
|
+
initial: defaults.cloudProvider === 'gcp' ? 0 : defaults.cloudProvider === 'aws' ? 1 : 2
|
|
317
439
|
},
|
|
318
440
|
{
|
|
319
441
|
type: (prev, values) => ['gcp', 'multi-cloud'].includes(values.cloudProvider) ? 'text' : null,
|
|
320
442
|
name: 'gcpProjectId',
|
|
321
443
|
message: '🌐 GCP Project ID (e.g., aaxis-rnd-non-prod):',
|
|
444
|
+
initial: defaults.gcpProjectId,
|
|
322
445
|
validate: value => value.trim().length > 0
|
|
323
446
|
},
|
|
324
447
|
{
|
|
325
448
|
type: (prev, values) => ['aws', 'multi-cloud'].includes(values.cloudProvider) ? 'text' : null,
|
|
326
449
|
name: 'awsAccountId',
|
|
327
450
|
message: '🌐 AWS Account ID (e.g., 929914624686):',
|
|
451
|
+
initial: defaults.awsAccountId,
|
|
328
452
|
validate: value => value.trim().length > 0
|
|
329
453
|
},
|
|
330
454
|
{
|
|
331
455
|
type: 'text',
|
|
332
456
|
name: 'region',
|
|
333
457
|
message: '🌍 Primary Region (e.g., us-central1 for GCP, us-east-1 for AWS):',
|
|
334
|
-
initial:
|
|
458
|
+
initial: defaults.region,
|
|
335
459
|
validate: value => value.trim().length > 0
|
|
336
460
|
},
|
|
337
461
|
{
|
|
338
462
|
type: 'text',
|
|
339
463
|
name: 'clusterName',
|
|
340
464
|
message: '☸️ Cluster Name (e.g., rnd-gke-nonprod or digital-eks-prod):',
|
|
465
|
+
initial: defaults.clusterName,
|
|
341
466
|
validate: value => value.trim().length > 0
|
|
342
467
|
},
|
|
343
468
|
{
|
|
@@ -345,12 +470,6 @@ async function runInteractiveWizard(detected) {
|
|
|
345
470
|
name: 'installClaudeCode',
|
|
346
471
|
message: '📥 Install Claude Code if not present?',
|
|
347
472
|
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
473
|
}
|
|
355
474
|
];
|
|
356
475
|
|
|
@@ -367,6 +486,10 @@ async function runInteractiveWizard(detected) {
|
|
|
367
486
|
process.exit(0);
|
|
368
487
|
}
|
|
369
488
|
|
|
489
|
+
// Add project context info to responses
|
|
490
|
+
responses.projectContextRepo = defaults.repoUrl;
|
|
491
|
+
responses.projectContextAlreadyCloned = !!projectContext;
|
|
492
|
+
|
|
370
493
|
return responses;
|
|
371
494
|
}
|
|
372
495
|
|
|
@@ -698,13 +821,58 @@ async function installClaudeAgentsPackage() {
|
|
|
698
821
|
|
|
699
822
|
/**
|
|
700
823
|
* Clone project context repository (optional)
|
|
824
|
+
* If already cloned during wizard, skip clone and just set it up in final location
|
|
701
825
|
*/
|
|
702
|
-
async function cloneProjectContextRepo(repoUrl) {
|
|
826
|
+
async function cloneProjectContextRepo(repoUrl, alreadyCloned = false) {
|
|
703
827
|
if (!repoUrl || repoUrl.trim() === '') {
|
|
704
828
|
console.log(chalk.gray('\n✓ Skipping project context repo clone (not provided)\n'));
|
|
705
829
|
return;
|
|
706
830
|
}
|
|
707
831
|
|
|
832
|
+
const sanitizedUrl = sanitizeGitUrl(repoUrl);
|
|
833
|
+
|
|
834
|
+
if (alreadyCloned) {
|
|
835
|
+
// Context was already cloned during wizard, just re-clone to final location
|
|
836
|
+
const spinner = ora('Setting up project context...').start();
|
|
837
|
+
|
|
838
|
+
try {
|
|
839
|
+
const projectContextDir = join(CWD, '.claude', 'project-context');
|
|
840
|
+
|
|
841
|
+
// Remove the generated project-context.json as it will be replaced by the cloned repo
|
|
842
|
+
const generatedFile = join(projectContextDir, 'project-context.json');
|
|
843
|
+
if (existsSync(generatedFile)) {
|
|
844
|
+
await fs.unlink(generatedFile);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Clone fresh to final location (we already validated it works)
|
|
848
|
+
const tempDir = `${projectContextDir}-temp`;
|
|
849
|
+
await execAsync(`git clone ${sanitizedUrl} ${tempDir}`, { timeout: 30000 });
|
|
850
|
+
|
|
851
|
+
// Move contents from temp to project-context
|
|
852
|
+
const files = await fs.readdir(tempDir);
|
|
853
|
+
for (const file of files) {
|
|
854
|
+
const src = join(tempDir, file);
|
|
855
|
+
const dest = join(projectContextDir, file);
|
|
856
|
+
await fs.rename(src, dest);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// Remove temp directory
|
|
860
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
861
|
+
|
|
862
|
+
spinner.succeed('Project context repository configured');
|
|
863
|
+
console.log(chalk.green(` → Location: .claude/project-context/\n`));
|
|
864
|
+
} catch (error) {
|
|
865
|
+
spinner.fail('Failed to setup project context repository');
|
|
866
|
+
console.log(chalk.yellow(`\n⚠️ You can clone it manually with:`));
|
|
867
|
+
console.log(chalk.gray(` cd .claude`));
|
|
868
|
+
console.log(chalk.gray(` rm -rf project-context`));
|
|
869
|
+
console.log(chalk.gray(` git clone ${sanitizedUrl} project-context\n`));
|
|
870
|
+
// Don't throw - allow installation to continue
|
|
871
|
+
}
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Not cloned yet during wizard, try to clone now
|
|
708
876
|
const spinner = ora('Cloning project context repository...').start();
|
|
709
877
|
|
|
710
878
|
try {
|
|
@@ -716,11 +884,11 @@ async function cloneProjectContextRepo(repoUrl) {
|
|
|
716
884
|
await fs.unlink(generatedFile);
|
|
717
885
|
}
|
|
718
886
|
|
|
719
|
-
// Clone repo
|
|
720
|
-
|
|
887
|
+
// Clone repo
|
|
888
|
+
const tempDir = `${projectContextDir}-temp`;
|
|
889
|
+
await execAsync(`git clone ${sanitizedUrl} ${tempDir}`, { timeout: 30000 });
|
|
721
890
|
|
|
722
891
|
// Move contents from temp to project-context
|
|
723
|
-
const tempDir = `${projectContextDir}-temp`;
|
|
724
892
|
const files = await fs.readdir(tempDir);
|
|
725
893
|
for (const file of files) {
|
|
726
894
|
const src = join(tempDir, file);
|
|
@@ -729,17 +897,22 @@ async function cloneProjectContextRepo(repoUrl) {
|
|
|
729
897
|
}
|
|
730
898
|
|
|
731
899
|
// Remove temp directory
|
|
732
|
-
await fs.
|
|
900
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
733
901
|
|
|
734
902
|
spinner.succeed('Project context repository cloned');
|
|
735
|
-
console.log(chalk.green(` → Cloned from: ${
|
|
903
|
+
console.log(chalk.green(` → Cloned from: ${sanitizedUrl}`));
|
|
736
904
|
console.log(chalk.gray(` → Location: .claude/project-context/\n`));
|
|
737
905
|
} catch (error) {
|
|
738
906
|
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(
|
|
907
|
+
console.log(chalk.yellow(`\n⚠️ Error: ${error.message}`));
|
|
908
|
+
console.log(chalk.gray('\n Common issues:'));
|
|
909
|
+
console.log(chalk.gray(' • Check SSH keys are configured: ssh -T git@bitbucket.org'));
|
|
910
|
+
console.log(chalk.gray(' • Verify repository URL is correct'));
|
|
911
|
+
console.log(chalk.gray(' • Ensure you have access to the repository\n'));
|
|
912
|
+
console.log(chalk.yellow(` You can clone it manually later with:`));
|
|
913
|
+
console.log(chalk.gray(` cd .claude`));
|
|
914
|
+
console.log(chalk.gray(` rm -rf project-context`));
|
|
915
|
+
console.log(chalk.gray(` git clone ${sanitizedUrl} project-context\n`));
|
|
743
916
|
// Don't throw - allow installation to continue
|
|
744
917
|
}
|
|
745
918
|
}
|
|
@@ -814,7 +987,7 @@ async function main() {
|
|
|
814
987
|
|
|
815
988
|
// Step 10: Clone project context repository (optional)
|
|
816
989
|
if (config.projectContextRepo) {
|
|
817
|
-
await cloneProjectContextRepo(config.projectContextRepo);
|
|
990
|
+
await cloneProjectContextRepo(config.projectContextRepo, config.projectContextAlreadyCloned);
|
|
818
991
|
}
|
|
819
992
|
|
|
820
993
|
// Success message
|