atlasia-ghost 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ghost.js +36 -18
- package/lib/setup-wizard.js +319 -0
- package/package.json +2 -1
package/ghost.js
CHANGED
|
@@ -103,25 +103,43 @@ class GatewayLauncher {
|
|
|
103
103
|
* - Wire up event handlers between components
|
|
104
104
|
* - Delegate to _initializeExtensions() for extension loading
|
|
105
105
|
*/
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
106
|
+
async initialize() {
|
|
107
|
+
try {
|
|
108
|
+
// Bootstrap environment directories
|
|
109
|
+
const ghostDir = path.dirname(USER_EXTENSIONS_DIR);
|
|
110
|
+
|
|
111
|
+
// Define required subdirectories
|
|
112
|
+
const dirs = {
|
|
113
|
+
home: ghostDir,
|
|
114
|
+
extensions: USER_EXTENSIONS_DIR,
|
|
115
|
+
telemetry: path.join(ghostDir, 'telemetry'),
|
|
116
|
+
config: path.join(ghostDir, 'config')
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Clean up any files that should be directories
|
|
120
|
+
for (const [key, dirPath] of Object.entries(dirs)) {
|
|
121
|
+
if (fs.existsSync(dirPath) && !fs.lstatSync(dirPath).isDirectory()) {
|
|
122
|
+
fs.unlinkSync(dirPath);
|
|
123
|
+
}
|
|
124
|
+
if (!fs.existsSync(dirPath)) {
|
|
125
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Bootstrap default config if missing
|
|
130
|
+
const configPath = path.join(dirs.config, 'config.json');
|
|
131
|
+
if (!fs.existsSync(configPath)) {
|
|
132
|
+
const defaultConfig = {
|
|
133
|
+
telemetry: { enabled: false, retention: '7d' },
|
|
134
|
+
extensions: { autoUpdate: false },
|
|
135
|
+
audit: { enabled: true, logPath: '~/.ghost/audit.log' }
|
|
136
|
+
};
|
|
137
|
+
fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2), 'utf8');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.warn(`[GatewayLauncher] Failed to bootstrap environment: ${error.message}`);
|
|
121
142
|
}
|
|
122
|
-
} catch (error) {
|
|
123
|
-
}
|
|
124
|
-
|
|
125
143
|
// Pure orchestration: component initialization
|
|
126
144
|
this.gateway = new Gateway({
|
|
127
145
|
extensionsDir: USER_EXTENSIONS_DIR,
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
const readline = require('readline');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
|
|
6
|
+
const Colors = {
|
|
7
|
+
HEADER: '\x1b[95m',
|
|
8
|
+
BLUE: '\x1b[94m',
|
|
9
|
+
CYAN: '\x1b[96m',
|
|
10
|
+
GREEN: '\x1b[92m',
|
|
11
|
+
WARNING: '\x1b[93m',
|
|
12
|
+
FAIL: '\x1b[91m',
|
|
13
|
+
ENDC: '\x1b[0m',
|
|
14
|
+
BOLD: '\x1b[1m',
|
|
15
|
+
DIM: '\x1b[2m'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const CONFIG_DIR = path.join(os.homedir(), '.ghost', 'config');
|
|
19
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'ghostrc.json');
|
|
20
|
+
const OLD_GHOSTRC = path.join(os.homedir(), '.ghostrc');
|
|
21
|
+
const OLD_VERSIONRC = path.join(os.homedir(), '.ghost-versionrc');
|
|
22
|
+
|
|
23
|
+
class SetupWizard {
|
|
24
|
+
constructor() {
|
|
25
|
+
this.rl = null;
|
|
26
|
+
this.config = {
|
|
27
|
+
workingDirectory: process.cwd(),
|
|
28
|
+
gitEditor: process.env.EDITOR || process.env.VISUAL || 'vim',
|
|
29
|
+
telemetry: {
|
|
30
|
+
enabled: true
|
|
31
|
+
},
|
|
32
|
+
ai: {
|
|
33
|
+
provider: null,
|
|
34
|
+
apiKey: null
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async run() {
|
|
40
|
+
console.log(`\n${Colors.BOLD}${Colors.CYAN}Ghost CLI Setup Wizard${Colors.ENDC}`);
|
|
41
|
+
console.log(`${Colors.DIM}${'─'.repeat(50)}${Colors.ENDC}\n`);
|
|
42
|
+
|
|
43
|
+
this.rl = readline.createInterface({
|
|
44
|
+
input: process.stdin,
|
|
45
|
+
output: process.stdout
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
await this.checkForMigration();
|
|
50
|
+
await this.promptWorkingDirectory();
|
|
51
|
+
await this.promptGitEditor();
|
|
52
|
+
await this.promptTelemetry();
|
|
53
|
+
await this.promptAIProvider();
|
|
54
|
+
await this.saveConfig();
|
|
55
|
+
|
|
56
|
+
console.log(`\n${Colors.GREEN}${Colors.BOLD}✓ Setup complete!${Colors.ENDC}`);
|
|
57
|
+
console.log(`${Colors.DIM}Configuration saved to: ${CONFIG_FILE}${Colors.ENDC}\n`);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(`\n${Colors.FAIL}Setup failed: ${error.message}${Colors.ENDC}\n`);
|
|
60
|
+
throw error;
|
|
61
|
+
} finally {
|
|
62
|
+
this.rl.close();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async checkForMigration() {
|
|
67
|
+
const oldFiles = [];
|
|
68
|
+
|
|
69
|
+
if (fs.existsSync(OLD_GHOSTRC)) {
|
|
70
|
+
oldFiles.push({ path: OLD_GHOSTRC, name: '.ghostrc' });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (fs.existsSync(OLD_VERSIONRC)) {
|
|
74
|
+
oldFiles.push({ path: OLD_VERSIONRC, name: '.ghost-versionrc' });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (oldFiles.length === 0) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log(`${Colors.WARNING}Found existing configuration files:${Colors.ENDC}`);
|
|
82
|
+
oldFiles.forEach(file => {
|
|
83
|
+
console.log(` - ${file.name}`);
|
|
84
|
+
});
|
|
85
|
+
console.log('');
|
|
86
|
+
|
|
87
|
+
const migrate = await this.prompt(`Would you like to migrate these settings? (y/n) [y]: `);
|
|
88
|
+
|
|
89
|
+
if (migrate.toLowerCase() === 'n') {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const file of oldFiles) {
|
|
94
|
+
try {
|
|
95
|
+
const content = fs.readFileSync(file.path, 'utf8');
|
|
96
|
+
const data = JSON.parse(content);
|
|
97
|
+
|
|
98
|
+
if (file.name === '.ghostrc' && data.workingDirectory) {
|
|
99
|
+
this.config.workingDirectory = data.workingDirectory;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (file.name === '.ghostrc' && data.gitEditor) {
|
|
103
|
+
this.config.gitEditor = data.gitEditor;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (file.name === '.ghostrc' && data.telemetry) {
|
|
107
|
+
this.config.telemetry = data.telemetry;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (file.name === '.ghostrc' && data.ai) {
|
|
111
|
+
this.config.ai = data.ai;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log(`${Colors.GREEN}✓${Colors.ENDC} Migrated settings from ${file.name}`);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.log(`${Colors.WARNING}⚠${Colors.ENDC} Could not parse ${file.name}: ${error.message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log('');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async promptWorkingDirectory() {
|
|
124
|
+
console.log(`${Colors.BOLD}Working Directory${Colors.ENDC}`);
|
|
125
|
+
console.log(`${Colors.DIM}Default directory for Git operations${Colors.ENDC}`);
|
|
126
|
+
|
|
127
|
+
const answer = await this.prompt(`Path [${this.config.workingDirectory}]: `);
|
|
128
|
+
|
|
129
|
+
if (answer.trim()) {
|
|
130
|
+
const expandedPath = answer.replace(/^~/, os.homedir());
|
|
131
|
+
const absolutePath = path.resolve(expandedPath);
|
|
132
|
+
|
|
133
|
+
if (!fs.existsSync(absolutePath)) {
|
|
134
|
+
console.log(`${Colors.WARNING}⚠ Directory does not exist: ${absolutePath}${Colors.ENDC}`);
|
|
135
|
+
const create = await this.prompt(`Create it? (y/n) [n]: `);
|
|
136
|
+
|
|
137
|
+
if (create.toLowerCase() === 'y') {
|
|
138
|
+
try {
|
|
139
|
+
fs.mkdirSync(absolutePath, { recursive: true });
|
|
140
|
+
console.log(`${Colors.GREEN}✓${Colors.ENDC} Directory created`);
|
|
141
|
+
this.config.workingDirectory = absolutePath;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.log(`${Colors.FAIL}✗${Colors.ENDC} Failed to create directory: ${error.message}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
this.config.workingDirectory = absolutePath;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log('');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async promptGitEditor() {
|
|
155
|
+
console.log(`${Colors.BOLD}Git Editor${Colors.ENDC}`);
|
|
156
|
+
console.log(`${Colors.DIM}Default editor for Git operations${Colors.ENDC}`);
|
|
157
|
+
|
|
158
|
+
const answer = await this.prompt(`Editor [${this.config.gitEditor}]: `);
|
|
159
|
+
|
|
160
|
+
if (answer.trim()) {
|
|
161
|
+
this.config.gitEditor = answer.trim();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log('');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async promptTelemetry() {
|
|
168
|
+
console.log(`${Colors.BOLD}Telemetry${Colors.ENDC}`);
|
|
169
|
+
console.log(`${Colors.DIM}Help improve Ghost CLI by sharing anonymous usage data${Colors.ENDC}`);
|
|
170
|
+
console.log(`${Colors.DIM}This includes command usage statistics and performance metrics${Colors.ENDC}`);
|
|
171
|
+
|
|
172
|
+
const defaultValue = this.config.telemetry.enabled ? 'y' : 'n';
|
|
173
|
+
const answer = await this.prompt(`Enable telemetry? (y/n) [${defaultValue}]: `);
|
|
174
|
+
|
|
175
|
+
if (answer.trim()) {
|
|
176
|
+
this.config.telemetry.enabled = answer.toLowerCase() === 'y';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log('');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async promptAIProvider() {
|
|
183
|
+
console.log(`${Colors.BOLD}AI Provider Configuration${Colors.ENDC}`);
|
|
184
|
+
console.log(`${Colors.DIM}Configure AI provider for enhanced Git features${Colors.ENDC}`);
|
|
185
|
+
console.log(`${Colors.DIM}Supported providers: groq, anthropic, gemini${Colors.ENDC}`);
|
|
186
|
+
|
|
187
|
+
const configure = await this.prompt(`Configure AI provider? (y/n) [n]: `);
|
|
188
|
+
|
|
189
|
+
if (configure.toLowerCase() !== 'y') {
|
|
190
|
+
console.log('');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log(`\n${Colors.BOLD}Available providers:${Colors.ENDC}`);
|
|
195
|
+
console.log(` 1. Groq (fast, open-source models)`);
|
|
196
|
+
console.log(` 2. Anthropic (Claude)`);
|
|
197
|
+
console.log(` 3. Gemini (Google AI)`);
|
|
198
|
+
console.log('');
|
|
199
|
+
|
|
200
|
+
const provider = await this.prompt(`Select provider (1-3) or name: `);
|
|
201
|
+
|
|
202
|
+
let providerName;
|
|
203
|
+
switch (provider.trim()) {
|
|
204
|
+
case '1':
|
|
205
|
+
providerName = 'groq';
|
|
206
|
+
break;
|
|
207
|
+
case '2':
|
|
208
|
+
providerName = 'anthropic';
|
|
209
|
+
break;
|
|
210
|
+
case '3':
|
|
211
|
+
providerName = 'gemini';
|
|
212
|
+
break;
|
|
213
|
+
default:
|
|
214
|
+
providerName = provider.trim().toLowerCase();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!providerName || !['groq', 'anthropic', 'gemini'].includes(providerName)) {
|
|
218
|
+
console.log(`${Colors.WARNING}⚠ Invalid provider, skipping AI configuration${Colors.ENDC}`);
|
|
219
|
+
console.log('');
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this.config.ai.provider = providerName;
|
|
224
|
+
|
|
225
|
+
console.log(`\n${Colors.DIM}Enter your ${providerName.toUpperCase()} API key${Colors.ENDC}`);
|
|
226
|
+
console.log(`${Colors.DIM}(input will be hidden)${Colors.ENDC}`);
|
|
227
|
+
|
|
228
|
+
const apiKey = await this.promptSecret(`API Key: `);
|
|
229
|
+
|
|
230
|
+
if (apiKey.trim()) {
|
|
231
|
+
this.config.ai.apiKey = apiKey.trim();
|
|
232
|
+
console.log(`${Colors.GREEN}✓${Colors.ENDC} API key configured`);
|
|
233
|
+
} else {
|
|
234
|
+
console.log(`${Colors.WARNING}⚠${Colors.ENDC} No API key provided, AI features will be disabled`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
console.log('');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async saveConfig() {
|
|
241
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
242
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const configData = JSON.stringify(this.config, null, 2);
|
|
246
|
+
fs.writeFileSync(CONFIG_FILE, configData, { mode: 0o600 });
|
|
247
|
+
|
|
248
|
+
console.log(`${Colors.GREEN}✓${Colors.ENDC} Configuration saved`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
prompt(question) {
|
|
252
|
+
return new Promise((resolve) => {
|
|
253
|
+
this.rl.question(question, (answer) => {
|
|
254
|
+
resolve(answer);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
promptSecret(question) {
|
|
260
|
+
return new Promise((resolve) => {
|
|
261
|
+
const stdin = process.stdin;
|
|
262
|
+
const wasRaw = stdin.isRaw;
|
|
263
|
+
|
|
264
|
+
if (stdin.setRawMode) {
|
|
265
|
+
stdin.setRawMode(true);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
process.stdout.write(question);
|
|
269
|
+
|
|
270
|
+
let input = '';
|
|
271
|
+
|
|
272
|
+
const onData = (char) => {
|
|
273
|
+
const charStr = char.toString('utf8');
|
|
274
|
+
|
|
275
|
+
if (charStr === '\n' || charStr === '\r' || charStr === '\u0004') {
|
|
276
|
+
if (stdin.setRawMode) {
|
|
277
|
+
stdin.setRawMode(wasRaw);
|
|
278
|
+
}
|
|
279
|
+
stdin.removeListener('data', onData);
|
|
280
|
+
process.stdout.write('\n');
|
|
281
|
+
resolve(input);
|
|
282
|
+
} else if (charStr === '\u0003') {
|
|
283
|
+
if (stdin.setRawMode) {
|
|
284
|
+
stdin.setRawMode(wasRaw);
|
|
285
|
+
}
|
|
286
|
+
stdin.removeListener('data', onData);
|
|
287
|
+
process.stdout.write('\n');
|
|
288
|
+
process.exit(0);
|
|
289
|
+
} else if (charStr === '\u007f' || charStr === '\b') {
|
|
290
|
+
if (input.length > 0) {
|
|
291
|
+
input = input.slice(0, -1);
|
|
292
|
+
process.stdout.write('\b \b');
|
|
293
|
+
}
|
|
294
|
+
} else if (charStr >= ' ' && charStr <= '~') {
|
|
295
|
+
input += charStr;
|
|
296
|
+
process.stdout.write('*');
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
stdin.on('data', onData);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
static loadConfig() {
|
|
305
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
const content = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
311
|
+
return JSON.parse(content);
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.error(`${Colors.WARNING}Warning: Could not load config: ${error.message}${Colors.ENDC}`);
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
module.exports = SetupWizard;
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atlasia-ghost",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Extensible gateway-based Git CLI with AI-powered operations",
|
|
5
5
|
"main": "ghost.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"ghost.js",
|
|
8
|
+
"lib/**/*.js",
|
|
8
9
|
"core/**/*.js",
|
|
9
10
|
"core/**/*.json",
|
|
10
11
|
"extensions/ghost-git-extension/**",
|