@jaguilar87/gaia-ops 1.0.0
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 +315 -0
- package/CLAUDE.md +154 -0
- package/LICENSE +21 -0
- package/README.md +221 -0
- package/agents/aws-troubleshooter.md +50 -0
- package/agents/claude-architect.md +821 -0
- package/agents/devops-developer.md +92 -0
- package/agents/gcp-troubleshooter.md +50 -0
- package/agents/gitops-operator.md +360 -0
- package/agents/terraform-architect.md +289 -0
- package/bin/gaia-init.js +620 -0
- package/commands/architect.md +97 -0
- package/commands/restore-session.md +87 -0
- package/commands/save-session.md +88 -0
- package/commands/session-status.md +61 -0
- package/commands/speckit.add-task.md +144 -0
- package/commands/speckit.analyze-task.md +65 -0
- package/commands/speckit.implement.md +96 -0
- package/commands/speckit.init.md +237 -0
- package/commands/speckit.plan.md +88 -0
- package/commands/speckit.specify.md +161 -0
- package/commands/speckit.tasks.md +188 -0
- package/config/AGENTS.md +162 -0
- package/config/agent-catalog.md +604 -0
- package/config/context-contracts.md +682 -0
- package/config/git-standards.md +674 -0
- package/config/git_standards.json +69 -0
- package/config/orchestration-workflow.md +735 -0
- package/hooks/__pycache__/post_tool_use.cpython-312.pyc +0 -0
- package/hooks/__pycache__/pre_kubectl_security.cpython-312.pyc +0 -0
- package/hooks/__pycache__/pre_tool_use.cpython-312.pyc +0 -0
- package/hooks/__pycache__/session_start.cpython-312.pyc +0 -0
- package/hooks/__pycache__/subagent_stop.cpython-312.pyc +0 -0
- package/hooks/post_tool_use.py +463 -0
- package/hooks/pre_kubectl_security.py +205 -0
- package/hooks/pre_tool_use.py +530 -0
- package/hooks/session_start.py +315 -0
- package/hooks/subagent_stop.py +549 -0
- package/index.js +92 -0
- package/package.json +59 -0
- package/speckit/README.en.md +648 -0
- package/speckit/README.md +353 -0
- package/speckit/governance.md +169 -0
- package/speckit/scripts/check-prerequisites.sh +194 -0
- package/speckit/scripts/common.sh +126 -0
- package/speckit/scripts/create-new-feature.sh +131 -0
- package/speckit/scripts/init.sh +42 -0
- package/speckit/scripts/setup-plan.sh +95 -0
- package/speckit/scripts/update-agent-context.sh +718 -0
- package/speckit/templates/adr-template.md +118 -0
- package/speckit/templates/agent-file-template.md +23 -0
- package/speckit/templates/plan-template.md +233 -0
- package/speckit/templates/spec-template.md +116 -0
- package/speckit/templates/tasks-template-bkp.md +136 -0
- package/speckit/templates/tasks-template.md +345 -0
- package/templates/CLAUDE.template.md +170 -0
- package/templates/code-examples/approval_gate_workflow.py +141 -0
- package/templates/code-examples/clarification_workflow.py +94 -0
- package/templates/code-examples/commit_validation.py +86 -0
- package/templates/project-context.template.json +126 -0
- package/templates/settings.template.json +307 -0
- package/tools/__pycache__/agent_router.cpython-312.pyc +0 -0
- package/tools/__pycache__/approval_gate.cpython-312.pyc +0 -0
- package/tools/__pycache__/clarify_engine.cpython-312.pyc +0 -0
- package/tools/__pycache__/clarify_patterns.cpython-312.pyc +0 -0
- package/tools/__pycache__/commit_validator.cpython-312.pyc +0 -0
- package/tools/__pycache__/context_section_reader.cpython-312.pyc +0 -0
- package/tools/__pycache__/routing_dashboard.cpython-312.pyc +0 -0
- package/tools/__pycache__/routing_feedback.cpython-312.pyc +0 -0
- package/tools/__pycache__/semantic_matcher.cpython-312.pyc +0 -0
- package/tools/__pycache__/task_manager.cpython-312.pyc +0 -0
- package/tools/agent_capabilities.json +231 -0
- package/tools/agent_invoker_helper.py +239 -0
- package/tools/agent_router.py +730 -0
- package/tools/approval_gate.py +318 -0
- package/tools/clarify_engine.py +511 -0
- package/tools/clarify_patterns.py +356 -0
- package/tools/commit_validator.py +338 -0
- package/tools/context_provider.py +181 -0
- package/tools/context_section_reader.py +301 -0
- package/tools/demo_clarify.py +104 -0
- package/tools/generate_embeddings.py +168 -0
- package/tools/quicktriage_aws_troubleshooter.sh +45 -0
- package/tools/quicktriage_devops_developer.sh +38 -0
- package/tools/quicktriage_gcp_troubleshooter.sh +51 -0
- package/tools/quicktriage_gitops_operator.sh +47 -0
- package/tools/quicktriage_terraform_architect.sh +40 -0
- package/tools/semantic_matcher.py +222 -0
- package/tools/task_manager.py +547 -0
- package/tools/task_manager_README.md +395 -0
- package/tools/task_manager_example.py +215 -0
package/bin/gaia-init.js
ADDED
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @jaguilar87/gaia-ops - Installer CLI
|
|
5
|
+
*
|
|
6
|
+
* Interactive installer for Gaia-Ops agent system
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx @jaguilar87/gaia-ops init # Interactive mode
|
|
10
|
+
* npx @jaguilar87/gaia-ops init --non-interactive # Non-interactive mode
|
|
11
|
+
* gaia-init # If installed globally
|
|
12
|
+
*
|
|
13
|
+
* CLI Options:
|
|
14
|
+
* --non-interactive Skip interactive prompts, use defaults or provided values
|
|
15
|
+
* --gitops <path> GitOps directory path
|
|
16
|
+
* --terraform <path> Terraform directory path
|
|
17
|
+
* --app-services <path> App services directory path
|
|
18
|
+
* --project-id <id> GCP Project ID
|
|
19
|
+
* --region <region> Primary region (default: us-central1)
|
|
20
|
+
* --cluster <name> Cluster name
|
|
21
|
+
* --skip-claude-install Skip Claude Code installation
|
|
22
|
+
*
|
|
23
|
+
* Environment Variables:
|
|
24
|
+
* CLAUDE_GITOPS_DIR GitOps directory path
|
|
25
|
+
* CLAUDE_TERRAFORM_DIR Terraform directory path
|
|
26
|
+
* CLAUDE_APP_SERVICES_DIR App services directory path
|
|
27
|
+
* CLAUDE_PROJECT_ID GCP Project ID
|
|
28
|
+
* CLAUDE_REGION Primary region
|
|
29
|
+
* CLAUDE_CLUSTER_NAME Cluster name
|
|
30
|
+
*
|
|
31
|
+
* Features:
|
|
32
|
+
* - Auto-detects project structure (GitOps, Terraform, AppServices)
|
|
33
|
+
* - Installs Claude Code if not present
|
|
34
|
+
* - Generates CLAUDE.md with correct paths
|
|
35
|
+
* - Creates .claude/ symlinks to npm package
|
|
36
|
+
* - Non-invasive: works from any directory
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { fileURLToPath } from 'url';
|
|
40
|
+
import { dirname, join, relative } from 'path';
|
|
41
|
+
import fs from 'fs/promises';
|
|
42
|
+
import { existsSync } from 'fs';
|
|
43
|
+
import { exec } from 'child_process';
|
|
44
|
+
import { promisify } from 'util';
|
|
45
|
+
import prompts from 'prompts';
|
|
46
|
+
import chalk from 'chalk';
|
|
47
|
+
import ora from 'ora';
|
|
48
|
+
import yargs from 'yargs';
|
|
49
|
+
import { hideBin } from 'yargs/helpers';
|
|
50
|
+
import { PACKAGE_ROOT, getTemplatePath } from '@jaguilar87/gaia-ops';
|
|
51
|
+
|
|
52
|
+
const execAsync = promisify(exec);
|
|
53
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
54
|
+
const __dirname = dirname(__filename);
|
|
55
|
+
|
|
56
|
+
const CWD = process.cwd();
|
|
57
|
+
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// Auto-detection logic
|
|
60
|
+
// ============================================================================
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Detect project structure by scanning for common directories
|
|
64
|
+
*/
|
|
65
|
+
async function detectProjectStructure() {
|
|
66
|
+
const spinner = ora('Detecting project structure...').start();
|
|
67
|
+
|
|
68
|
+
const detected = {
|
|
69
|
+
gitops: null,
|
|
70
|
+
terraform: null,
|
|
71
|
+
appServices: null,
|
|
72
|
+
git: null
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const entries = await fs.readdir(CWD);
|
|
77
|
+
|
|
78
|
+
// GitOps candidates
|
|
79
|
+
const gitopsCandidates = [
|
|
80
|
+
'gitops',
|
|
81
|
+
'non-prod-rnd-gke-gitops',
|
|
82
|
+
'k8s',
|
|
83
|
+
'kubernetes',
|
|
84
|
+
'manifests',
|
|
85
|
+
'deployments'
|
|
86
|
+
];
|
|
87
|
+
for (const candidate of gitopsCandidates) {
|
|
88
|
+
if (entries.includes(candidate)) {
|
|
89
|
+
detected.gitops = `./${candidate}`;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Terraform candidates
|
|
95
|
+
const terraformCandidates = [
|
|
96
|
+
'terraform',
|
|
97
|
+
'tf',
|
|
98
|
+
'infrastructure',
|
|
99
|
+
'iac',
|
|
100
|
+
'infra'
|
|
101
|
+
];
|
|
102
|
+
for (const candidate of terraformCandidates) {
|
|
103
|
+
if (entries.includes(candidate)) {
|
|
104
|
+
detected.terraform = `./${candidate}`;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// AppServices candidates
|
|
110
|
+
const appServicesCandidates = [
|
|
111
|
+
'app-services',
|
|
112
|
+
'services',
|
|
113
|
+
'apps',
|
|
114
|
+
'applications',
|
|
115
|
+
'src'
|
|
116
|
+
];
|
|
117
|
+
for (const candidate of appServicesCandidates) {
|
|
118
|
+
if (entries.includes(candidate)) {
|
|
119
|
+
detected.appServices = `./${candidate}`;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Git root
|
|
125
|
+
if (entries.includes('.git')) {
|
|
126
|
+
detected.git = '.';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
spinner.succeed('Project structure detected');
|
|
130
|
+
} catch (error) {
|
|
131
|
+
spinner.fail('Failed to detect project structure');
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return detected;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check if Claude Code is installed
|
|
140
|
+
*/
|
|
141
|
+
async function checkClaudeCodeInstalled() {
|
|
142
|
+
try {
|
|
143
|
+
// Try 'claude' command first (common alias)
|
|
144
|
+
await execAsync('claude --version');
|
|
145
|
+
return true;
|
|
146
|
+
} catch {
|
|
147
|
+
try {
|
|
148
|
+
// Fallback to 'claude-code' command
|
|
149
|
+
await execAsync('claude-code --version');
|
|
150
|
+
return true;
|
|
151
|
+
} catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ============================================================================
|
|
158
|
+
// CLI Argument Parsing
|
|
159
|
+
// ============================================================================
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Parse CLI arguments
|
|
163
|
+
*/
|
|
164
|
+
function parseCliArguments() {
|
|
165
|
+
return yargs(hideBin(process.argv))
|
|
166
|
+
.usage('Usage: $0 [options]')
|
|
167
|
+
.option('non-interactive', {
|
|
168
|
+
alias: 'y',
|
|
169
|
+
type: 'boolean',
|
|
170
|
+
description: 'Skip interactive prompts, use defaults or provided values',
|
|
171
|
+
default: false
|
|
172
|
+
})
|
|
173
|
+
.option('gitops', {
|
|
174
|
+
type: 'string',
|
|
175
|
+
description: 'GitOps directory path'
|
|
176
|
+
})
|
|
177
|
+
.option('terraform', {
|
|
178
|
+
type: 'string',
|
|
179
|
+
description: 'Terraform directory path'
|
|
180
|
+
})
|
|
181
|
+
.option('app-services', {
|
|
182
|
+
type: 'string',
|
|
183
|
+
description: 'App services directory path'
|
|
184
|
+
})
|
|
185
|
+
.option('project-id', {
|
|
186
|
+
type: 'string',
|
|
187
|
+
description: 'GCP Project ID'
|
|
188
|
+
})
|
|
189
|
+
.option('region', {
|
|
190
|
+
type: 'string',
|
|
191
|
+
description: 'Primary region (default: us-central1)',
|
|
192
|
+
default: 'us-central1'
|
|
193
|
+
})
|
|
194
|
+
.option('cluster', {
|
|
195
|
+
type: 'string',
|
|
196
|
+
description: 'Cluster name'
|
|
197
|
+
})
|
|
198
|
+
.option('skip-claude-install', {
|
|
199
|
+
type: 'boolean',
|
|
200
|
+
description: 'Skip Claude Code installation',
|
|
201
|
+
default: false
|
|
202
|
+
})
|
|
203
|
+
.help('h')
|
|
204
|
+
.alias('h', 'help')
|
|
205
|
+
.version('1.0.0')
|
|
206
|
+
.alias('v', 'version')
|
|
207
|
+
.parse();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get configuration from CLI args, environment variables, and detected values
|
|
212
|
+
*/
|
|
213
|
+
function getConfiguration(detected, args) {
|
|
214
|
+
// Priority: CLI args > Environment variables > Detected values > Defaults
|
|
215
|
+
|
|
216
|
+
const config = {
|
|
217
|
+
gitops: args.gitops || process.env.CLAUDE_GITOPS_DIR || detected.gitops || './gitops',
|
|
218
|
+
terraform: args.terraform || process.env.CLAUDE_TERRAFORM_DIR || detected.terraform || './terraform',
|
|
219
|
+
appServices: args.appServices || process.env.CLAUDE_APP_SERVICES_DIR || detected.appServices || './app-services',
|
|
220
|
+
projectId: args.projectId || process.env.CLAUDE_PROJECT_ID || '',
|
|
221
|
+
region: args.region || process.env.CLAUDE_REGION || 'us-central1',
|
|
222
|
+
clusterName: args.cluster || process.env.CLAUDE_CLUSTER_NAME || '',
|
|
223
|
+
installClaudeCode: !args.skipClaudeInstall
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return config;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Validate required configuration values
|
|
231
|
+
*/
|
|
232
|
+
function validateConfiguration(config, nonInteractive) {
|
|
233
|
+
const errors = [];
|
|
234
|
+
|
|
235
|
+
if (!config.projectId && nonInteractive) {
|
|
236
|
+
errors.push('--project-id is required in non-interactive mode');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (!config.clusterName && nonInteractive) {
|
|
240
|
+
errors.push('--cluster is required in non-interactive mode');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (errors.length > 0) {
|
|
244
|
+
console.error(chalk.red('\nā Configuration errors:\n'));
|
|
245
|
+
errors.forEach(error => console.error(chalk.red(` - ${error}`)));
|
|
246
|
+
console.error(chalk.gray('\nProvide values via CLI args or environment variables.\n'));
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ============================================================================
|
|
254
|
+
// Interactive prompts
|
|
255
|
+
// ============================================================================
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Present interactive wizard to user
|
|
259
|
+
*/
|
|
260
|
+
async function runInteractiveWizard(detected) {
|
|
261
|
+
console.log(chalk.cyan.bold('\nš¤ Claude Code Agent System Installer\n'));
|
|
262
|
+
console.log(chalk.gray('This wizard will set up the Claude Code agent system for your project.\n'));
|
|
263
|
+
|
|
264
|
+
const questions = [
|
|
265
|
+
{
|
|
266
|
+
type: 'text',
|
|
267
|
+
name: 'gitops',
|
|
268
|
+
message: 'š¦ GitOps directory (Flux/Kubernetes manifests):',
|
|
269
|
+
initial: detected.gitops || './gitops',
|
|
270
|
+
validate: value => value.trim().length > 0
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
type: 'text',
|
|
274
|
+
name: 'terraform',
|
|
275
|
+
message: 'š§ Terraform directory (infrastructure as code):',
|
|
276
|
+
initial: detected.terraform || './terraform',
|
|
277
|
+
validate: value => value.trim().length > 0
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
type: 'text',
|
|
281
|
+
name: 'appServices',
|
|
282
|
+
message: 'š App Services directory (application code):',
|
|
283
|
+
initial: detected.appServices || './app-services',
|
|
284
|
+
validate: value => value.trim().length > 0
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
type: 'text',
|
|
288
|
+
name: 'projectId',
|
|
289
|
+
message: 'š GCP Project ID (e.g., aaxis-rnd-non-prod):',
|
|
290
|
+
validate: value => value.trim().length > 0
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
type: 'text',
|
|
294
|
+
name: 'region',
|
|
295
|
+
message: 'š Primary Region (e.g., us-central1):',
|
|
296
|
+
initial: 'us-central1',
|
|
297
|
+
validate: value => value.trim().length > 0
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
type: 'text',
|
|
301
|
+
name: 'clusterName',
|
|
302
|
+
message: 'āøļø Cluster Name (e.g., non-prod-rnd-gke):',
|
|
303
|
+
validate: value => value.trim().length > 0
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
type: 'confirm',
|
|
307
|
+
name: 'installClaudeCode',
|
|
308
|
+
message: 'š„ Install Claude Code if not present?',
|
|
309
|
+
initial: true
|
|
310
|
+
}
|
|
311
|
+
];
|
|
312
|
+
|
|
313
|
+
const responses = await prompts(questions);
|
|
314
|
+
|
|
315
|
+
// User cancelled
|
|
316
|
+
if (Object.keys(responses).length < questions.length) {
|
|
317
|
+
console.log(chalk.yellow('\nā ļø Installation cancelled by user\n'));
|
|
318
|
+
process.exit(0);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return responses;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ============================================================================
|
|
325
|
+
// Installation steps
|
|
326
|
+
// ============================================================================
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Install Claude Code CLI
|
|
330
|
+
*/
|
|
331
|
+
async function installClaudeCode() {
|
|
332
|
+
const spinner = ora('Installing Claude Code...').start();
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
await execAsync('npm install -g @anthropic-ai/claude-code');
|
|
336
|
+
spinner.succeed('Claude Code installed successfully');
|
|
337
|
+
} catch (error) {
|
|
338
|
+
spinner.fail('Failed to install Claude Code');
|
|
339
|
+
console.error(chalk.red('\nError:'), error.message);
|
|
340
|
+
console.log(chalk.yellow('\nPlease install Claude Code manually:'));
|
|
341
|
+
console.log(chalk.gray(' npm install -g @anthropic-ai/claude-code\n'));
|
|
342
|
+
throw error;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Create .claude/ directory structure with symlinks
|
|
348
|
+
*/
|
|
349
|
+
async function createClaudeDirectory() {
|
|
350
|
+
const spinner = ora('Creating .claude/ directory...').start();
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const claudeDir = join(CWD, '.claude');
|
|
354
|
+
|
|
355
|
+
// Create base directory
|
|
356
|
+
await fs.mkdir(claudeDir, { recursive: true });
|
|
357
|
+
|
|
358
|
+
// Calculate relative path from .claude/ to node_modules/@jaguilar87/gaia-ops
|
|
359
|
+
const packagePath = join(CWD, 'node_modules', '@jaguilar87', 'gaia-ops');
|
|
360
|
+
const relativePath = relative(claudeDir, packagePath);
|
|
361
|
+
|
|
362
|
+
// Create symlinks to npm package
|
|
363
|
+
const symlinks = [
|
|
364
|
+
{ target: join(relativePath, 'agents'), link: join(claudeDir, 'agents') },
|
|
365
|
+
{ target: join(relativePath, 'tools'), link: join(claudeDir, 'tools') },
|
|
366
|
+
{ target: join(relativePath, 'hooks'), link: join(claudeDir, 'hooks') },
|
|
367
|
+
{ target: join(relativePath, 'commands'), link: join(claudeDir, 'commands') },
|
|
368
|
+
{ target: join(relativePath, 'templates'), link: join(claudeDir, 'templates') },
|
|
369
|
+
{ target: join(relativePath, 'config'), link: join(claudeDir, 'config') },
|
|
370
|
+
{ target: join(relativePath, 'speckit'), link: join(claudeDir, 'speckit') },
|
|
371
|
+
{ target: join(relativePath, 'CHANGELOG.md'), link: join(claudeDir, 'CHANGELOG.md') }
|
|
372
|
+
];
|
|
373
|
+
|
|
374
|
+
for (const { target, link } of symlinks) {
|
|
375
|
+
// Remove existing symlink if present
|
|
376
|
+
if (existsSync(link)) {
|
|
377
|
+
await fs.unlink(link);
|
|
378
|
+
}
|
|
379
|
+
await fs.symlink(target, link);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Create project-specific directories (NOT symlinked)
|
|
383
|
+
await fs.mkdir(join(claudeDir, 'logs'), { recursive: true });
|
|
384
|
+
await fs.mkdir(join(claudeDir, 'tests'), { recursive: true });
|
|
385
|
+
|
|
386
|
+
spinner.succeed('.claude/ directory created with symlinks');
|
|
387
|
+
} catch (error) {
|
|
388
|
+
spinner.fail('Failed to create .claude/ directory');
|
|
389
|
+
throw error;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Generate CLAUDE.md from template
|
|
395
|
+
*/
|
|
396
|
+
async function generateClaudeMd(config) {
|
|
397
|
+
const spinner = ora('Generating CLAUDE.md...').start();
|
|
398
|
+
|
|
399
|
+
try {
|
|
400
|
+
// Read template
|
|
401
|
+
const templatePath = getTemplatePath('CLAUDE.template.md');
|
|
402
|
+
let template = await fs.readFile(templatePath, 'utf-8');
|
|
403
|
+
|
|
404
|
+
// Replace placeholders
|
|
405
|
+
template = template.replace(/{{PROJECT_ID}}/g, config.projectId);
|
|
406
|
+
template = template.replace(/{{REGION}}/g, config.region);
|
|
407
|
+
template = template.replace(/{{CLUSTER_NAME}}/g, config.clusterName);
|
|
408
|
+
template = template.replace(/{{GITOPS_PATH}}/g, config.gitops);
|
|
409
|
+
template = template.replace(/{{TERRAFORM_PATH}}/g, config.terraform);
|
|
410
|
+
template = template.replace(/{{APP_SERVICES_PATH}}/g, config.appServices);
|
|
411
|
+
|
|
412
|
+
// Write to current directory
|
|
413
|
+
const claudeMdPath = join(CWD, 'CLAUDE.md');
|
|
414
|
+
await fs.writeFile(claudeMdPath, template, 'utf-8');
|
|
415
|
+
|
|
416
|
+
spinner.succeed('CLAUDE.md generated');
|
|
417
|
+
} catch (error) {
|
|
418
|
+
spinner.fail('Failed to generate CLAUDE.md');
|
|
419
|
+
throw error;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Generate AGENTS.md symlink
|
|
425
|
+
*/
|
|
426
|
+
async function generateAgentsMd() {
|
|
427
|
+
const spinner = ora('Creating AGENTS.md symlink...').start();
|
|
428
|
+
|
|
429
|
+
try {
|
|
430
|
+
const agentsMdLink = join(CWD, 'AGENTS.md');
|
|
431
|
+
const packagePath = join(CWD, 'node_modules', '@jaguilar87', 'gaia-ops', 'config', 'AGENTS.md');
|
|
432
|
+
const relativePath = relative(CWD, packagePath);
|
|
433
|
+
|
|
434
|
+
// Remove existing if present
|
|
435
|
+
if (existsSync(agentsMdLink)) {
|
|
436
|
+
await fs.unlink(agentsMdLink);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
await fs.symlink(relativePath, agentsMdLink);
|
|
440
|
+
|
|
441
|
+
spinner.succeed('AGENTS.md symlink created');
|
|
442
|
+
} catch (error) {
|
|
443
|
+
spinner.fail('Failed to create AGENTS.md symlink');
|
|
444
|
+
throw error;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Generate project-context.json
|
|
450
|
+
*/
|
|
451
|
+
async function generateProjectContext(config) {
|
|
452
|
+
const spinner = ora('Generating project-context.json...').start();
|
|
453
|
+
|
|
454
|
+
try {
|
|
455
|
+
const projectContext = {
|
|
456
|
+
version: '1.0',
|
|
457
|
+
last_updated: new Date().toISOString(),
|
|
458
|
+
project_details: {
|
|
459
|
+
id: config.projectId,
|
|
460
|
+
region: config.region,
|
|
461
|
+
environment: 'non-prod',
|
|
462
|
+
cluster_name: config.clusterName
|
|
463
|
+
},
|
|
464
|
+
terraform_infrastructure: {
|
|
465
|
+
layout: {
|
|
466
|
+
base_path: config.terraform,
|
|
467
|
+
module_structure: 'terragrunt'
|
|
468
|
+
},
|
|
469
|
+
provider_credentials: {
|
|
470
|
+
gcp: {
|
|
471
|
+
project: config.projectId,
|
|
472
|
+
region: config.region
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
gitops_configuration: {
|
|
477
|
+
repository: {
|
|
478
|
+
path: config.gitops,
|
|
479
|
+
platform: 'flux'
|
|
480
|
+
},
|
|
481
|
+
flux_details: {
|
|
482
|
+
namespaces: []
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
application_services: {
|
|
486
|
+
base_path: config.appServices,
|
|
487
|
+
services: []
|
|
488
|
+
},
|
|
489
|
+
operational_guidelines: {
|
|
490
|
+
commit_standards: {
|
|
491
|
+
format: 'conventional_commits',
|
|
492
|
+
validation_required: true,
|
|
493
|
+
config_path: '.claude/config/git_standards.json'
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const projectContextPath = join(CWD, '.claude', 'project-context.json');
|
|
499
|
+
await fs.writeFile(projectContextPath, JSON.stringify(projectContext, null, 2), 'utf-8');
|
|
500
|
+
|
|
501
|
+
spinner.succeed('project-context.json generated');
|
|
502
|
+
} catch (error) {
|
|
503
|
+
spinner.fail('Failed to generate project-context.json');
|
|
504
|
+
throw error;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Install @jaguilar87/gaia-ops as dependency
|
|
510
|
+
*/
|
|
511
|
+
async function installClaudeAgentsPackage() {
|
|
512
|
+
const spinner = ora('Installing @jaguilar87/gaia-ops package...').start();
|
|
513
|
+
|
|
514
|
+
try {
|
|
515
|
+
// Check if package.json exists
|
|
516
|
+
const packageJsonPath = join(CWD, 'package.json');
|
|
517
|
+
if (!existsSync(packageJsonPath)) {
|
|
518
|
+
// Create minimal package.json
|
|
519
|
+
const packageJson = {
|
|
520
|
+
name: 'my-project',
|
|
521
|
+
version: '1.0.0',
|
|
522
|
+
private: true,
|
|
523
|
+
dependencies: {}
|
|
524
|
+
};
|
|
525
|
+
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf-8');
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Install @jaguilar87/gaia-ops
|
|
529
|
+
// NOTE: In production, this would install from npm registry
|
|
530
|
+
// For now, we'll use local path
|
|
531
|
+
await execAsync('npm install @jaguilar87/gaia-ops');
|
|
532
|
+
|
|
533
|
+
spinner.succeed('@jaguilar87/gaia-ops package installed');
|
|
534
|
+
} catch (error) {
|
|
535
|
+
spinner.fail('Failed to install @jaguilar87/gaia-ops');
|
|
536
|
+
throw error;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// ============================================================================
|
|
541
|
+
// Main installer flow
|
|
542
|
+
// ============================================================================
|
|
543
|
+
|
|
544
|
+
async function main() {
|
|
545
|
+
try {
|
|
546
|
+
// Parse CLI arguments
|
|
547
|
+
const args = parseCliArguments();
|
|
548
|
+
|
|
549
|
+
// Clear console unless in non-interactive mode
|
|
550
|
+
if (!args.nonInteractive) {
|
|
551
|
+
console.clear();
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Step 1: Check Claude Code installation
|
|
555
|
+
const claudeCodeInstalled = await checkClaudeCodeInstalled();
|
|
556
|
+
if (!claudeCodeInstalled) {
|
|
557
|
+
console.log(chalk.yellow('ā ļø Claude Code is not installed\n'));
|
|
558
|
+
} else {
|
|
559
|
+
console.log(chalk.green('ā
Claude Code is already installed\n'));
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Step 2: Auto-detect project structure
|
|
563
|
+
const detected = await detectProjectStructure();
|
|
564
|
+
|
|
565
|
+
// Step 3: Get configuration (CLI args, env vars, detected, or interactive)
|
|
566
|
+
let config;
|
|
567
|
+
if (args.nonInteractive) {
|
|
568
|
+
// Non-interactive mode: use CLI args, env vars, and detected values
|
|
569
|
+
config = getConfiguration(detected, args);
|
|
570
|
+
validateConfiguration(config, true);
|
|
571
|
+
|
|
572
|
+
// Display configuration being used
|
|
573
|
+
console.log(chalk.cyan('\nš Configuration:\n'));
|
|
574
|
+
console.log(chalk.gray(` GitOps: ${config.gitops}`));
|
|
575
|
+
console.log(chalk.gray(` Terraform: ${config.terraform}`));
|
|
576
|
+
console.log(chalk.gray(` App Services: ${config.appServices}`));
|
|
577
|
+
console.log(chalk.gray(` Project ID: ${config.projectId}`));
|
|
578
|
+
console.log(chalk.gray(` Region: ${config.region}`));
|
|
579
|
+
console.log(chalk.gray(` Cluster: ${config.clusterName}\n`));
|
|
580
|
+
} else {
|
|
581
|
+
// Interactive mode: run wizard with pre-filled detected values
|
|
582
|
+
config = await runInteractiveWizard(detected);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Step 4: Install Claude Code if requested
|
|
586
|
+
if (config.installClaudeCode && !claudeCodeInstalled) {
|
|
587
|
+
await installClaudeCode();
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Step 5: Install @jaguilar87/gaia-ops package
|
|
591
|
+
await installClaudeAgentsPackage();
|
|
592
|
+
|
|
593
|
+
// Step 6: Create .claude/ directory with symlinks
|
|
594
|
+
await createClaudeDirectory();
|
|
595
|
+
|
|
596
|
+
// Step 7: Generate CLAUDE.md
|
|
597
|
+
await generateClaudeMd(config);
|
|
598
|
+
|
|
599
|
+
// Step 8: Generate AGENTS.md symlink
|
|
600
|
+
await generateAgentsMd();
|
|
601
|
+
|
|
602
|
+
// Step 9: Generate project-context.json
|
|
603
|
+
await generateProjectContext(config);
|
|
604
|
+
|
|
605
|
+
// Success message
|
|
606
|
+
console.log(chalk.green.bold('\nā
Installation complete!\n'));
|
|
607
|
+
console.log(chalk.gray('Next steps:'));
|
|
608
|
+
console.log(chalk.gray(' 1. Review CLAUDE.md and adjust paths if needed'));
|
|
609
|
+
console.log(chalk.gray(' 2. Update .claude/project-context.json with your services'));
|
|
610
|
+
console.log(chalk.gray(' 3. Start Claude Code: claude-code\n'));
|
|
611
|
+
console.log(chalk.cyan('š Documentation: .claude/config/\n'));
|
|
612
|
+
|
|
613
|
+
} catch (error) {
|
|
614
|
+
console.error(chalk.red('\nā Installation failed\n'));
|
|
615
|
+
console.error(error);
|
|
616
|
+
process.exit(1);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
main();
|