chati-dev 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/assets/logo.txt +6 -0
- package/bin/chati.js +175 -0
- package/framework/agents/build/dev.md +342 -0
- package/framework/agents/clarity/architect.md +263 -0
- package/framework/agents/clarity/brief.md +277 -0
- package/framework/agents/clarity/brownfield-wu.md +288 -0
- package/framework/agents/clarity/detail.md +274 -0
- package/framework/agents/clarity/greenfield-wu.md +231 -0
- package/framework/agents/clarity/phases.md +272 -0
- package/framework/agents/clarity/tasks.md +279 -0
- package/framework/agents/clarity/ux.md +293 -0
- package/framework/agents/deploy/devops.md +321 -0
- package/framework/agents/quality/qa-implementation.md +310 -0
- package/framework/agents/quality/qa-planning.md +289 -0
- package/framework/config.yaml +8 -0
- package/framework/constitution.md +238 -0
- package/framework/frameworks/decision-heuristics.yaml +64 -0
- package/framework/frameworks/quality-dimensions.yaml +59 -0
- package/framework/i18n/en.yaml +78 -0
- package/framework/i18n/es.yaml +78 -0
- package/framework/i18n/fr.yaml +78 -0
- package/framework/i18n/pt.yaml +78 -0
- package/framework/intelligence/confidence.yaml +42 -0
- package/framework/intelligence/gotchas.yaml +51 -0
- package/framework/intelligence/patterns.yaml +32 -0
- package/framework/migrations/v1.0-to-v1.1.yaml +48 -0
- package/framework/orchestrator/chati.md +333 -0
- package/framework/patterns/elicitation.md +137 -0
- package/framework/quality-gates/implementation-gate.md +64 -0
- package/framework/quality-gates/planning-gate.md +52 -0
- package/framework/schemas/config.schema.json +42 -0
- package/framework/schemas/session.schema.json +103 -0
- package/framework/schemas/task.schema.json +71 -0
- package/framework/templates/brownfield-prd-tmpl.yaml +103 -0
- package/framework/templates/fullstack-architecture-tmpl.yaml +101 -0
- package/framework/templates/prd-tmpl.yaml +94 -0
- package/framework/templates/qa-gate-tmpl.yaml +96 -0
- package/framework/templates/task-tmpl.yaml +85 -0
- package/framework/workflows/brownfield-discovery.yaml +75 -0
- package/framework/workflows/brownfield-fullstack.yaml +104 -0
- package/framework/workflows/brownfield-service.yaml +81 -0
- package/framework/workflows/brownfield-ui.yaml +87 -0
- package/framework/workflows/greenfield-fullstack.yaml +108 -0
- package/package.json +60 -0
- package/scripts/bundle-framework.js +58 -0
- package/src/config/ide-configs.js +80 -0
- package/src/config/mcp-configs.js +136 -0
- package/src/dashboard/data-reader.js +99 -0
- package/src/dashboard/layout.js +161 -0
- package/src/dashboard/renderer.js +104 -0
- package/src/installer/core.js +221 -0
- package/src/installer/templates.js +97 -0
- package/src/installer/validator.js +114 -0
- package/src/upgrade/backup.js +107 -0
- package/src/upgrade/checker.js +105 -0
- package/src/upgrade/migrator.js +171 -0
- package/src/utils/colors.js +18 -0
- package/src/utils/detector.js +51 -0
- package/src/utils/logger.js +41 -0
- package/src/wizard/feedback.js +76 -0
- package/src/wizard/i18n.js +168 -0
- package/src/wizard/index.js +107 -0
- package/src/wizard/questions.js +169 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
import semver from 'semver';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Find applicable migrations between two versions
|
|
8
|
+
*/
|
|
9
|
+
export function findMigrations(targetDir, fromVersion, toVersion) {
|
|
10
|
+
const migrationsDir = join(targetDir, 'chati.dev', 'migrations');
|
|
11
|
+
if (!existsSync(migrationsDir)) return [];
|
|
12
|
+
|
|
13
|
+
const files = readdirSync(migrationsDir)
|
|
14
|
+
.filter(f => f.endsWith('.yaml'))
|
|
15
|
+
.sort();
|
|
16
|
+
|
|
17
|
+
const applicable = [];
|
|
18
|
+
|
|
19
|
+
for (const file of files) {
|
|
20
|
+
try {
|
|
21
|
+
const content = yaml.load(readFileSync(join(migrationsDir, file), 'utf-8'));
|
|
22
|
+
const migration = content?.migration;
|
|
23
|
+
|
|
24
|
+
if (!migration?.from || !migration?.to) continue;
|
|
25
|
+
|
|
26
|
+
// Check if this migration is in the upgrade path
|
|
27
|
+
if (
|
|
28
|
+
semver.gte(migration.from, fromVersion) &&
|
|
29
|
+
semver.lte(migration.to, toVersion)
|
|
30
|
+
) {
|
|
31
|
+
applicable.push({
|
|
32
|
+
file,
|
|
33
|
+
from: migration.from,
|
|
34
|
+
to: migration.to,
|
|
35
|
+
description: migration.description || '',
|
|
36
|
+
breaking: migration.breaking || false,
|
|
37
|
+
steps: content.steps || [],
|
|
38
|
+
rollback: content.rollback || [],
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// Skip invalid migration files
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Sort by target version
|
|
47
|
+
applicable.sort((a, b) => semver.compare(a.to, b.to));
|
|
48
|
+
|
|
49
|
+
return applicable;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Execute a single migration step
|
|
54
|
+
*/
|
|
55
|
+
function executeMigrationStep(targetDir, step) {
|
|
56
|
+
switch (step.type) {
|
|
57
|
+
case 'create_directory': {
|
|
58
|
+
const dir = join(targetDir, step.path);
|
|
59
|
+
if (!existsSync(dir) || !step.idempotent) {
|
|
60
|
+
mkdirSync(dir, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
case 'create_file': {
|
|
66
|
+
const filePath = join(targetDir, step.path);
|
|
67
|
+
if (!existsSync(filePath) || !step.skip_if_exists) {
|
|
68
|
+
mkdirSync(join(targetDir, step.path, '..'), { recursive: true });
|
|
69
|
+
writeFileSync(filePath, step.content || '', 'utf-8');
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
case 'update_yaml': {
|
|
75
|
+
const filePath = join(targetDir, step.path);
|
|
76
|
+
if (!existsSync(filePath)) break;
|
|
77
|
+
|
|
78
|
+
const content = yaml.load(readFileSync(filePath, 'utf-8')) || {};
|
|
79
|
+
|
|
80
|
+
if (step.operation === 'add_field') {
|
|
81
|
+
const keys = step.field.split('.');
|
|
82
|
+
let obj = content;
|
|
83
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
84
|
+
if (!obj[keys[i]]) obj[keys[i]] = {};
|
|
85
|
+
obj = obj[keys[i]];
|
|
86
|
+
}
|
|
87
|
+
const lastKey = keys[keys.length - 1];
|
|
88
|
+
if (!step.skip_if_exists || !(lastKey in obj)) {
|
|
89
|
+
obj[lastKey] = step.default;
|
|
90
|
+
}
|
|
91
|
+
} else if (step.operation === 'set') {
|
|
92
|
+
const keys = step.field.split('.');
|
|
93
|
+
let obj = content;
|
|
94
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
95
|
+
if (!obj[keys[i]]) obj[keys[i]] = {};
|
|
96
|
+
obj = obj[keys[i]];
|
|
97
|
+
}
|
|
98
|
+
obj[keys[keys.length - 1]] = step.value;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
writeFileSync(filePath, yaml.dump(content, { lineWidth: -1 }), 'utf-8');
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
case 'delete_directory': {
|
|
106
|
+
const dir = join(targetDir, step.path);
|
|
107
|
+
if (existsSync(dir)) {
|
|
108
|
+
if (step.only_if_empty) {
|
|
109
|
+
const files = readdirSync(dir);
|
|
110
|
+
if (files.length === 0) {
|
|
111
|
+
rmSync(dir, { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
rmSync(dir, { recursive: true });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
default:
|
|
121
|
+
// Unknown step type - skip
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Run all applicable migrations sequentially
|
|
128
|
+
*/
|
|
129
|
+
export async function runMigrations(targetDir, fromVersion, toVersion) {
|
|
130
|
+
const migrations = findMigrations(targetDir, fromVersion, toVersion);
|
|
131
|
+
|
|
132
|
+
if (migrations.length === 0) {
|
|
133
|
+
return { success: true, migrationsRun: 0 };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const results = [];
|
|
137
|
+
|
|
138
|
+
for (const migration of migrations) {
|
|
139
|
+
try {
|
|
140
|
+
for (const step of migration.steps) {
|
|
141
|
+
executeMigrationStep(targetDir, step);
|
|
142
|
+
}
|
|
143
|
+
results.push({ file: migration.file, success: true });
|
|
144
|
+
} catch (err) {
|
|
145
|
+
results.push({ file: migration.file, success: false, error: err.message });
|
|
146
|
+
|
|
147
|
+
// Attempt rollback for this migration
|
|
148
|
+
try {
|
|
149
|
+
for (const step of migration.rollback) {
|
|
150
|
+
executeMigrationStep(targetDir, step);
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
// Rollback failed - return error
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
migrationsRun: results.filter(r => r.success).length,
|
|
159
|
+
failedAt: migration.file,
|
|
160
|
+
error: err.message,
|
|
161
|
+
results,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
success: true,
|
|
168
|
+
migrationsRun: migrations.length,
|
|
169
|
+
results,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
// chati.dev brand color: steel blue
|
|
4
|
+
export const brand = chalk.hex('#6BA4DC');
|
|
5
|
+
export const dim = chalk.dim;
|
|
6
|
+
export const bold = chalk.bold;
|
|
7
|
+
export const green = chalk.green;
|
|
8
|
+
export const red = chalk.red;
|
|
9
|
+
export const yellow = chalk.yellow;
|
|
10
|
+
export const cyan = chalk.cyan;
|
|
11
|
+
export const gray = chalk.gray;
|
|
12
|
+
export const white = chalk.white;
|
|
13
|
+
|
|
14
|
+
export const success = chalk.green;
|
|
15
|
+
export const error = chalk.red;
|
|
16
|
+
export const warning = chalk.yellow;
|
|
17
|
+
export const info = chalk.hex('#6BA4DC');
|
|
18
|
+
export const muted = chalk.dim;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Auto-detect project type (greenfield vs brownfield)
|
|
6
|
+
* Brownfield signals: package.json, src/, .git/, etc.
|
|
7
|
+
*/
|
|
8
|
+
export function detectProjectType(targetDir) {
|
|
9
|
+
const signals = {
|
|
10
|
+
packageJson: existsSync(join(targetDir, 'package.json')),
|
|
11
|
+
srcDir: existsSync(join(targetDir, 'src')),
|
|
12
|
+
gitDir: existsSync(join(targetDir, '.git')),
|
|
13
|
+
composerJson: existsSync(join(targetDir, 'composer.json')),
|
|
14
|
+
requirementsTxt: existsSync(join(targetDir, 'requirements.txt')),
|
|
15
|
+
goMod: existsSync(join(targetDir, 'go.mod')),
|
|
16
|
+
cargoToml: existsSync(join(targetDir, 'Cargo.toml')),
|
|
17
|
+
pomXml: existsSync(join(targetDir, 'pom.xml')),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const brownfieldSignals = Object.values(signals).filter(Boolean).length;
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
suggestion: brownfieldSignals >= 2 ? 'brownfield' : 'greenfield',
|
|
24
|
+
signals,
|
|
25
|
+
confidence: brownfieldSignals >= 2 ? 'high' : brownfieldSignals === 1 ? 'medium' : 'low',
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Detect currently installed IDEs by checking common paths/commands
|
|
31
|
+
*/
|
|
32
|
+
export function detectInstalledIDEs() {
|
|
33
|
+
const detected = [];
|
|
34
|
+
|
|
35
|
+
// Claude Code - check if claude CLI exists
|
|
36
|
+
if (existsSync('/usr/local/bin/claude') || existsSync(join(process.env.HOME || '', '.claude'))) {
|
|
37
|
+
detected.push('claude-code');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// VS Code - check .vscode or code command
|
|
41
|
+
if (existsSync(join(process.env.HOME || '', '.vscode'))) {
|
|
42
|
+
detected.push('vscode');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Cursor
|
|
46
|
+
if (existsSync(join(process.env.HOME || '', '.cursor'))) {
|
|
47
|
+
detected.push('cursor');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return detected;
|
|
51
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { success, error, warning, info, muted, brand, dim } from './colors.js';
|
|
2
|
+
|
|
3
|
+
export function logStep(message) {
|
|
4
|
+
console.log(` ${success('✓')} ${message}`);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function logError(message) {
|
|
8
|
+
console.log(` ${error('✗')} ${message}`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function logWarning(message) {
|
|
12
|
+
console.log(` ${warning('⚠')} ${message}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function logInfo(message) {
|
|
16
|
+
console.log(` ${info('ℹ')} ${message}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function logMuted(message) {
|
|
20
|
+
console.log(` ${muted(message)}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function logBanner(logoText, version) {
|
|
24
|
+
const lines = logoText.trim().split('\n');
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
console.log(brand(line));
|
|
27
|
+
}
|
|
28
|
+
console.log(brand(`chati.dev v${version}`));
|
|
29
|
+
console.log(dim('AI-Powered Multi-Agent Development Framework'));
|
|
30
|
+
console.log(dim('═'.repeat(55)));
|
|
31
|
+
console.log();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function logSection(title) {
|
|
35
|
+
console.log();
|
|
36
|
+
console.log(` ${brand('──')} ${title} ${brand('─'.repeat(Math.max(0, 45 - title.length)))}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function logResult(label, value) {
|
|
40
|
+
console.log(` ${muted(label + ':')} ${value}`);
|
|
41
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
import { brand, success, dim } from '../utils/colors.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create a spinner with chati.dev branding
|
|
6
|
+
*/
|
|
7
|
+
export function createSpinner(text) {
|
|
8
|
+
return ora({
|
|
9
|
+
text,
|
|
10
|
+
color: 'cyan',
|
|
11
|
+
spinner: 'dots',
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Show installation progress step
|
|
17
|
+
*/
|
|
18
|
+
export function showStep(message) {
|
|
19
|
+
console.log(` ${success('✓')} ${message}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Show validation step
|
|
24
|
+
*/
|
|
25
|
+
export function showValidation(message) {
|
|
26
|
+
console.log(` ${success('✓')} ${message}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Show warning
|
|
31
|
+
*/
|
|
32
|
+
export function showWarning(message) {
|
|
33
|
+
console.log(` ${brand('⚠')} ${message}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Show error
|
|
38
|
+
*/
|
|
39
|
+
export function showError(message) {
|
|
40
|
+
console.log(` ${'✗'} ${message}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Show quick start guide
|
|
45
|
+
*/
|
|
46
|
+
export function showQuickStart(title, steps) {
|
|
47
|
+
console.log();
|
|
48
|
+
console.log(`${brand(title)}:`);
|
|
49
|
+
steps.forEach((step, i) => {
|
|
50
|
+
console.log(` ${dim(`${i + 1}.`)} ${step}`);
|
|
51
|
+
});
|
|
52
|
+
console.log();
|
|
53
|
+
console.log(`${dim('Documentation:')} chati.dev/constitution.md`);
|
|
54
|
+
console.log(`${dim('Session:')} .chati/session.yaml`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Show summary table
|
|
59
|
+
*/
|
|
60
|
+
export function showSummary(data) {
|
|
61
|
+
console.log();
|
|
62
|
+
for (const [label, value] of Object.entries(data)) {
|
|
63
|
+
const paddedLabel = label.padEnd(12);
|
|
64
|
+
console.log(` ${dim(paddedLabel)} ${value}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Show checklist of items to install
|
|
70
|
+
*/
|
|
71
|
+
export function showChecklist(items) {
|
|
72
|
+
console.log();
|
|
73
|
+
for (const item of items) {
|
|
74
|
+
console.log(` ${success('✓')} ${item}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import yaml from 'js-yaml';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
// Bundled fallback strings (English) for when i18n files aren't available
|
|
9
|
+
const FALLBACK_EN = {
|
|
10
|
+
installer: {
|
|
11
|
+
welcome: 'Welcome to chati.dev',
|
|
12
|
+
select_language: 'Select your language:',
|
|
13
|
+
project_type: 'What type of project are you setting up?',
|
|
14
|
+
greenfield: 'Greenfield (new project from scratch)',
|
|
15
|
+
brownfield: 'Brownfield (existing project integration)',
|
|
16
|
+
detected_brownfield: 'Detected package.json and src/ in current directory',
|
|
17
|
+
suggestion_brownfield: 'Suggestion: Brownfield',
|
|
18
|
+
select_ides: 'Select IDE(s) for chati.dev configuration:',
|
|
19
|
+
select_mcps: 'Select MCPs to install (project-level):',
|
|
20
|
+
confirmation_title: 'Installation Summary',
|
|
21
|
+
project_label: 'Project',
|
|
22
|
+
language_label: 'Language',
|
|
23
|
+
ides_label: 'IDEs',
|
|
24
|
+
mcps_label: 'MCPs',
|
|
25
|
+
will_install: 'Will install:',
|
|
26
|
+
agents_count: '13 agent definitions (CLARITY, BUILD, DEPLOY phases)',
|
|
27
|
+
workflows_count: '5 workflow blueprints',
|
|
28
|
+
templates_count: '5 templates (PRD, Brownfield PRD, Architecture, Task, QA Gate)',
|
|
29
|
+
constitution: 'Constitution (10 Articles + Preamble)',
|
|
30
|
+
session_mgmt: 'Session management system',
|
|
31
|
+
quality_gates: 'Quality gates (4-tier validation)',
|
|
32
|
+
proceed: 'Proceed with installation?',
|
|
33
|
+
installing: 'Installing chati.dev...',
|
|
34
|
+
created_chati: 'Created .chati/ session directory',
|
|
35
|
+
created_framework: 'Created chati.dev/ framework directory (agents, templates, workflows)',
|
|
36
|
+
created_commands: 'Created .claude/commands/ (thin router)',
|
|
37
|
+
installed_constitution: 'Installed Constitution (Articles I-X)',
|
|
38
|
+
created_session: 'Created session.yaml schema',
|
|
39
|
+
created_claude_md: 'Created CLAUDE.md',
|
|
40
|
+
configured_mcps: 'Configured MCPs:',
|
|
41
|
+
validating: 'Validating installation...',
|
|
42
|
+
agents_valid: 'All 13 agents implement 8 protocols',
|
|
43
|
+
handoff_ok: 'Handoff protocol: OK',
|
|
44
|
+
validation_ok: 'Self-validation criteria: OK',
|
|
45
|
+
constitution_ok: 'Constitution: 10 articles verified',
|
|
46
|
+
session_ok: 'Session schema: valid',
|
|
47
|
+
success: 'chati.dev installed successfully!',
|
|
48
|
+
quick_start_title: 'Quick Start',
|
|
49
|
+
quick_start_1: 'Open your IDE',
|
|
50
|
+
quick_start_2: 'Type: /chati',
|
|
51
|
+
quick_start_3: 'The orchestrator will guide you through the process',
|
|
52
|
+
},
|
|
53
|
+
agents: {
|
|
54
|
+
starting: 'Starting agent: {agent}',
|
|
55
|
+
completed: 'Agent {agent} completed with score {score}%',
|
|
56
|
+
refining: 'Refining artifacts for consistency...',
|
|
57
|
+
running_validations: 'Running additional validations...',
|
|
58
|
+
blocked: 'Blocker detected: {blocker}',
|
|
59
|
+
},
|
|
60
|
+
options: {
|
|
61
|
+
continue: 'Continue with {agent} (Recommended)',
|
|
62
|
+
review: 'Review last result',
|
|
63
|
+
status: 'View full status',
|
|
64
|
+
enter_prompt: 'Enter the number or describe what you want to do:',
|
|
65
|
+
},
|
|
66
|
+
status: {
|
|
67
|
+
project: 'Project',
|
|
68
|
+
type: 'Type',
|
|
69
|
+
phase: 'Phase',
|
|
70
|
+
mode: 'Mode',
|
|
71
|
+
language: 'Language',
|
|
72
|
+
ide: 'IDE',
|
|
73
|
+
current_agent: 'Current Agent',
|
|
74
|
+
last_handoff: 'Last Handoff',
|
|
75
|
+
backlog_items: 'Backlog Items',
|
|
76
|
+
high_priority: 'high priority',
|
|
77
|
+
},
|
|
78
|
+
errors: {
|
|
79
|
+
session_corrupted: 'Session file appears corrupted. Attempting recovery...',
|
|
80
|
+
handoff_missing: 'Handoff not found. Using session.yaml + CLAUDE.md as fallback.',
|
|
81
|
+
agent_failed: 'Agent failed after 3 attempts. Escalating to user.',
|
|
82
|
+
mcp_required: "Required MCP '{mcp}' is not configured. Installation instructions:",
|
|
83
|
+
mcp_optional: "Optional MCP '{mcp}' not configured. Skipping related functionality.",
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
let currentStrings = FALLBACK_EN;
|
|
88
|
+
let currentLanguage = 'en';
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Load i18n strings for a given language
|
|
92
|
+
* Tries: 1) installed chati.dev/i18n/ in target project, 2) bundled assets, 3) fallback
|
|
93
|
+
*/
|
|
94
|
+
export function loadLanguage(lang, targetDir = null) {
|
|
95
|
+
currentLanguage = lang;
|
|
96
|
+
|
|
97
|
+
if (lang === 'en') {
|
|
98
|
+
currentStrings = FALLBACK_EN;
|
|
99
|
+
return currentStrings;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Try to load from installed project i18n
|
|
103
|
+
const paths = [
|
|
104
|
+
targetDir && join(targetDir, 'chati.dev', 'i18n', `${lang}.yaml`),
|
|
105
|
+
join(__dirname, '..', '..', 'assets', 'i18n', `${lang}.yaml`),
|
|
106
|
+
].filter(Boolean);
|
|
107
|
+
|
|
108
|
+
for (const filePath of paths) {
|
|
109
|
+
if (existsSync(filePath)) {
|
|
110
|
+
try {
|
|
111
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
112
|
+
const parsed = yaml.load(content);
|
|
113
|
+
currentStrings = parsed;
|
|
114
|
+
return currentStrings;
|
|
115
|
+
} catch {
|
|
116
|
+
// Fall through to next path
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Fallback to English
|
|
122
|
+
currentStrings = FALLBACK_EN;
|
|
123
|
+
return currentStrings;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get a translated string by dot-notation key
|
|
128
|
+
* Supports interpolation: t('agents.completed', { agent: 'Brief', score: 97 })
|
|
129
|
+
*/
|
|
130
|
+
export function t(key, vars = {}) {
|
|
131
|
+
const keys = key.split('.');
|
|
132
|
+
let value = currentStrings;
|
|
133
|
+
|
|
134
|
+
for (const k of keys) {
|
|
135
|
+
if (value && typeof value === 'object' && k in value) {
|
|
136
|
+
value = value[k];
|
|
137
|
+
} else {
|
|
138
|
+
// Fallback to English
|
|
139
|
+
value = FALLBACK_EN;
|
|
140
|
+
for (const fk of keys) {
|
|
141
|
+
if (value && typeof value === 'object' && fk in value) {
|
|
142
|
+
value = value[fk];
|
|
143
|
+
} else {
|
|
144
|
+
return key; // Return key itself if not found
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (typeof value !== 'string') return key;
|
|
152
|
+
|
|
153
|
+
// Interpolate variables
|
|
154
|
+
return value.replace(/\{(\w+)\}/g, (_, varName) => {
|
|
155
|
+
return vars[varName] !== undefined ? String(vars[varName]) : `{${varName}}`;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function getCurrentLanguage() {
|
|
160
|
+
return currentLanguage;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const SUPPORTED_LANGUAGES = [
|
|
164
|
+
{ value: 'en', label: 'English' },
|
|
165
|
+
{ value: 'pt', label: 'Portugues' },
|
|
166
|
+
{ value: 'es', label: 'Espanol' },
|
|
167
|
+
{ value: 'fr', label: 'Francais' },
|
|
168
|
+
];
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { join, dirname, basename } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { logBanner } from '../utils/logger.js';
|
|
6
|
+
import { stepLanguage, stepProjectType, stepIDEs, stepMCPs, stepConfirmation } from './questions.js';
|
|
7
|
+
import { createSpinner, showStep, showValidation, showQuickStart } from './feedback.js';
|
|
8
|
+
import { installFramework } from '../installer/core.js';
|
|
9
|
+
import { validateInstallation } from '../installer/validator.js';
|
|
10
|
+
import { t } from './i18n.js';
|
|
11
|
+
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const VERSION = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf-8')).version;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Run the 6-step installer wizard
|
|
17
|
+
*/
|
|
18
|
+
export async function runWizard(targetDir, options = {}) {
|
|
19
|
+
// Load ASCII logo
|
|
20
|
+
let logoText;
|
|
21
|
+
try {
|
|
22
|
+
logoText = readFileSync(join(__dirname, '..', '..', 'assets', 'logo.txt'), 'utf-8');
|
|
23
|
+
} catch {
|
|
24
|
+
logoText = 'chati.dev';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Step 1: Logo + Language Selection (in English)
|
|
28
|
+
logBanner(logoText, VERSION);
|
|
29
|
+
|
|
30
|
+
p.intro('Setting up chati.dev');
|
|
31
|
+
|
|
32
|
+
const language = options.language || await stepLanguage();
|
|
33
|
+
|
|
34
|
+
// Step 2: Project Type
|
|
35
|
+
const projectType = options.projectType || await stepProjectType(targetDir);
|
|
36
|
+
|
|
37
|
+
// Step 3: IDE Selection
|
|
38
|
+
const selectedIDEs = options.ides || await stepIDEs();
|
|
39
|
+
|
|
40
|
+
// Step 4: MCP Selection
|
|
41
|
+
const selectedMCPs = options.mcps || await stepMCPs(projectType);
|
|
42
|
+
|
|
43
|
+
// Step 5: Confirmation
|
|
44
|
+
const projectName = basename(targetDir);
|
|
45
|
+
const config = {
|
|
46
|
+
projectName,
|
|
47
|
+
projectType,
|
|
48
|
+
language,
|
|
49
|
+
selectedIDEs,
|
|
50
|
+
selectedMCPs,
|
|
51
|
+
targetDir,
|
|
52
|
+
version: VERSION,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
await stepConfirmation(config);
|
|
56
|
+
|
|
57
|
+
// Step 6: Installation + Validation
|
|
58
|
+
console.log();
|
|
59
|
+
const installSpinner = createSpinner(t('installer.installing'));
|
|
60
|
+
installSpinner.start();
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
await installFramework(config);
|
|
64
|
+
installSpinner.stop();
|
|
65
|
+
|
|
66
|
+
showStep(t('installer.created_chati'));
|
|
67
|
+
showStep(t('installer.created_framework'));
|
|
68
|
+
showStep(t('installer.created_commands'));
|
|
69
|
+
showStep(t('installer.installed_constitution'));
|
|
70
|
+
showStep(t('installer.created_session'));
|
|
71
|
+
showStep(t('installer.created_claude_md'));
|
|
72
|
+
|
|
73
|
+
if (selectedMCPs.length > 0) {
|
|
74
|
+
showStep(`${t('installer.configured_mcps')} ${selectedMCPs.join(', ')}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Validation
|
|
78
|
+
console.log();
|
|
79
|
+
const validateSpinner = createSpinner(t('installer.validating'));
|
|
80
|
+
validateSpinner.start();
|
|
81
|
+
|
|
82
|
+
const validation = await validateInstallation(targetDir);
|
|
83
|
+
validateSpinner.stop();
|
|
84
|
+
|
|
85
|
+
showValidation(t('installer.agents_valid'));
|
|
86
|
+
showValidation(t('installer.handoff_ok'));
|
|
87
|
+
showValidation(t('installer.validation_ok'));
|
|
88
|
+
showValidation(t('installer.constitution_ok'));
|
|
89
|
+
showValidation(t('installer.session_ok'));
|
|
90
|
+
|
|
91
|
+
console.log();
|
|
92
|
+
p.outro(t('installer.success'));
|
|
93
|
+
|
|
94
|
+
const primaryIDE = selectedIDEs[0] || 'your IDE';
|
|
95
|
+
showQuickStart(t('installer.quick_start_title'), [
|
|
96
|
+
`${t('installer.quick_start_1')} (${primaryIDE})`,
|
|
97
|
+
t('installer.quick_start_2'),
|
|
98
|
+
t('installer.quick_start_3'),
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
return { success: true, config, validation };
|
|
102
|
+
} catch (err) {
|
|
103
|
+
installSpinner.stop();
|
|
104
|
+
p.cancel(`Installation failed: ${err.message}`);
|
|
105
|
+
return { success: false, error: err.message };
|
|
106
|
+
}
|
|
107
|
+
}
|