clevermation-cli 0.3.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.
@@ -0,0 +1,429 @@
1
+ import { Command } from 'commander';
2
+ import { input, select, checkbox, confirm } from '@inquirer/prompts';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import fs from 'fs-extra';
6
+ import path from 'path';
7
+ import os from 'os';
8
+ import { execa } from 'execa';
9
+ import { logger } from '../utils/logger.js';
10
+ import { saveProjectConfig, getProjectConfigDir, loadGlobalConfig } from '../utils/config.js';
11
+ import type {
12
+ ProjectConfig,
13
+ ServiceType,
14
+ ProjectType,
15
+ ModelType,
16
+ } from '../types/config.js';
17
+
18
+ interface InitOptions {
19
+ name?: string;
20
+ customer?: string;
21
+ yes?: boolean;
22
+ }
23
+
24
+ export function createInitCommand(): Command {
25
+ return new Command('init')
26
+ .description('Initialisiere ein neues Clevermation Projekt')
27
+ .option('-n, --name <name>', 'Projektname')
28
+ .option('-c, --customer <customer>', 'Kundenname (für Kundenprojekte)')
29
+ .option('-y, --yes', 'Alle Defaults akzeptieren')
30
+ .action(async (options: InitOptions) => {
31
+ await runInit(options);
32
+ });
33
+ }
34
+
35
+ async function runInit(options: InitOptions) {
36
+ logger.title('Clevermation Project Setup');
37
+
38
+ // Prüfen ob bereits ein Projekt existiert
39
+ if (await fs.pathExists(getProjectConfigDir())) {
40
+ logger.warning('Es existiert bereits ein Clevermation Projekt in diesem Verzeichnis.');
41
+ const overwrite = await confirm({
42
+ message: 'Möchtest du die Konfiguration überschreiben?',
43
+ default: false,
44
+ });
45
+ if (!overwrite) {
46
+ logger.info('Setup abgebrochen.');
47
+ return;
48
+ }
49
+ }
50
+
51
+ // Step 1: Projekt-Typ
52
+ const projectType = (await select({
53
+ message: 'Projekt-Typ',
54
+ choices: [
55
+ { name: 'Kundenprojekt (kunde-projekt)', value: 'customer' },
56
+ { name: 'Internes Projekt (clevermation-name)', value: 'internal' },
57
+ ],
58
+ })) as ProjectType;
59
+
60
+ // Step 2: Projektname
61
+ const projectName =
62
+ options.name ||
63
+ (await input({
64
+ message: 'Projektname',
65
+ validate: (v) =>
66
+ /^[a-z0-9-]+$/.test(v) ||
67
+ 'Nur Kleinbuchstaben, Zahlen und Bindestriche erlaubt',
68
+ }));
69
+
70
+ // Step 3: Kundenname (falls Kundenprojekt)
71
+ let customerName: string | undefined;
72
+ if (projectType === 'customer') {
73
+ customerName =
74
+ options.customer ||
75
+ (await input({
76
+ message: 'Kundenname',
77
+ validate: (v) =>
78
+ /^[a-z0-9-]+$/.test(v) ||
79
+ 'Nur Kleinbuchstaben, Zahlen und Bindestriche erlaubt',
80
+ }));
81
+ }
82
+
83
+ // Step 4: Services auswählen (Checkboxen)
84
+ const services = (await checkbox({
85
+ message: 'Wähle die Services für dein Projekt',
86
+ choices: [
87
+ { name: 'Supabase (Datenbank, Auth, Storage)', value: 'supabase' },
88
+ { name: 'N8N (Workflow Automation)', value: 'n8n' },
89
+ { name: 'ElevenLabs (Voice AI)', value: 'elevenlabs' },
90
+ ],
91
+ })) as ServiceType[];
92
+
93
+ // Step 5: Claude Model
94
+ const modelPreference = (await select({
95
+ message: 'Standard Claude Model für dieses Projekt',
96
+ choices: [
97
+ { name: 'Opus - Höchste Qualität, komplexe Tasks', value: 'opus' },
98
+ { name: 'Sonnet - Ausgewogen (empfohlen)', value: 'sonnet' },
99
+ { name: 'Haiku - Schnell und kostengünstig', value: 'haiku' },
100
+ ],
101
+ default: 'sonnet',
102
+ })) as ModelType;
103
+
104
+ // Step 6: Optimale Einstellungen
105
+ // Prüfe ob in globaler Config default gesetzt ist
106
+ const globalConfig = await loadGlobalConfig();
107
+ const defaultOptimal = globalConfig?.preferences?.optimaleEinstellungen ?? true;
108
+
109
+ const optimaleEinstellungen = await confirm({
110
+ message: 'Optimale Projekt-Einstellungen vornehmen?',
111
+ default: defaultOptimal,
112
+ });
113
+
114
+ // Step 7: Zusammenfassung
115
+ const repoName =
116
+ projectType === 'customer'
117
+ ? `${customerName}-${projectName}`
118
+ : `clevermation-${projectName}`;
119
+
120
+ logger.blank();
121
+ logger.dim('Setup Zusammenfassung:');
122
+ logger.dim(` Repo: Clevermation/${repoName}`);
123
+ logger.dim(
124
+ ` Services: GitHub${services.length > 0 ? ', ' + services.join(', ') : ''}`
125
+ );
126
+ logger.dim(` Model: ${modelPreference}`);
127
+ logger.blank();
128
+
129
+ const proceed = await confirm({
130
+ message: 'Projekt mit diesen Einstellungen erstellen?',
131
+ default: true,
132
+ });
133
+
134
+ if (!proceed) {
135
+ logger.warning('Setup abgebrochen.');
136
+ return;
137
+ }
138
+
139
+ // Step 7: Projekt erstellen
140
+ const spinner = ora('Erstelle Projektstruktur...').start();
141
+
142
+ try {
143
+ // Verzeichnisse erstellen
144
+ await createProjectStructure();
145
+ spinner.text = 'Richte Claude Code Plugins ein...';
146
+
147
+ // Claude Code Konfiguration
148
+ await setupClaudeCode(services, modelPreference);
149
+
150
+ // Optimale Einstellungen (wenn gewählt)
151
+ if (optimaleEinstellungen) {
152
+ spinner.text = 'Wende optimale Einstellungen an...';
153
+ await applyOptimalSettings();
154
+ }
155
+
156
+ spinner.text = 'Speichere Projektkonfiguration...';
157
+
158
+ // Projekt Config speichern
159
+ const config: ProjectConfig = {
160
+ version: '1.0.0',
161
+ project: {
162
+ name: projectName,
163
+ type: projectType,
164
+ customer: customerName,
165
+ },
166
+ services: {
167
+ github: {
168
+ enabled: true,
169
+ repo: repoName,
170
+ org: 'Clevermation',
171
+ },
172
+ supabase: services.includes('supabase')
173
+ ? { enabled: true }
174
+ : { enabled: false },
175
+ n8n: services.includes('n8n') ? { enabled: true } : { enabled: false },
176
+ elevenlabs: services.includes('elevenlabs')
177
+ ? { enabled: true }
178
+ : { enabled: false },
179
+ },
180
+ claudeCode: {
181
+ plugins: getPluginsForServices(services),
182
+ marketplace: 'Clevermation/clevermation-claude-plugins',
183
+ model: modelPreference,
184
+ },
185
+ createdAt: new Date().toISOString(),
186
+ updatedAt: new Date().toISOString(),
187
+ };
188
+
189
+ await saveProjectConfig(config);
190
+
191
+ // Git initialisieren (falls noch nicht vorhanden)
192
+ if (!(await fs.pathExists('.git'))) {
193
+ spinner.text = 'Initialisiere Git Repository...';
194
+ await execa('git', ['init']);
195
+ }
196
+
197
+ spinner.succeed('Projekt erfolgreich erstellt!');
198
+
199
+ // Nächste Schritte anzeigen
200
+ showNextSteps(repoName, services);
201
+ } catch (error) {
202
+ spinner.fail('Setup fehlgeschlagen');
203
+ throw error;
204
+ }
205
+ }
206
+
207
+ async function createProjectStructure(): Promise<void> {
208
+ const dirs = [
209
+ '.clevermation',
210
+ '.claude',
211
+ '.claude/agents',
212
+ '.claude/skills',
213
+ '.claude/commands',
214
+ 'src',
215
+ 'docs',
216
+ ];
217
+
218
+ for (const dir of dirs) {
219
+ await fs.ensureDir(dir);
220
+ }
221
+
222
+ // .gitignore erstellen/erweitern
223
+ const gitignoreContent = `# Dependencies
224
+ node_modules/
225
+ .bun/
226
+
227
+ # Build
228
+ dist/
229
+ .output/
230
+
231
+ # Environment
232
+ .env
233
+ .env.local
234
+ .env.*.local
235
+
236
+ # Claude Code Local Settings
237
+ .claude/settings.local.json
238
+
239
+ # OS
240
+ .DS_Store
241
+ Thumbs.db
242
+
243
+ # IDE
244
+ .idea/
245
+ .vscode/
246
+ *.swp
247
+ *.swo
248
+ `;
249
+
250
+ const gitignorePath = '.gitignore';
251
+ if (await fs.pathExists(gitignorePath)) {
252
+ const existing = await fs.readFile(gitignorePath, 'utf-8');
253
+ if (!existing.includes('.claude/settings.local.json')) {
254
+ await fs.appendFile(gitignorePath, '\n# Claude Code\n.claude/settings.local.json\n');
255
+ }
256
+ } else {
257
+ await fs.writeFile(gitignorePath, gitignoreContent);
258
+ }
259
+ }
260
+
261
+ async function setupClaudeCode(
262
+ services: ServiceType[],
263
+ model: ModelType
264
+ ): Promise<void> {
265
+ // .claude/settings.json erstellen
266
+ const settings = {
267
+ permissions: {
268
+ allow: ['Bash(npm*)', 'Bash(bun*)', 'Bash(git*)'],
269
+ deny: [],
270
+ },
271
+ agents: {
272
+ defaultModel: model,
273
+ },
274
+ };
275
+
276
+ await fs.writeJson('.claude/settings.json', settings, { spaces: 2 });
277
+
278
+ // .claude/settings.local.json Template erstellen
279
+ const envVars: Record<string, string> = {
280
+ FIRECRAWL_API_KEY: '',
281
+ };
282
+
283
+ if (services.includes('supabase')) {
284
+ envVars.SUPABASE_URL = '';
285
+ envVars.SUPABASE_SECRET_KEY = '';
286
+ envVars.SUPABASE_PUBLISHABLE_KEY = '';
287
+ }
288
+
289
+ if (services.includes('n8n')) {
290
+ envVars.N8N_URL = '';
291
+ envVars.N8N_API_KEY = '';
292
+ }
293
+
294
+ if (services.includes('elevenlabs')) {
295
+ envVars.ELEVENLABS_API_KEY = '';
296
+ }
297
+
298
+ await fs.writeJson('.claude/settings.local.json', { env: envVars }, { spaces: 2 });
299
+
300
+ // .mcp.json erstellen
301
+ await createMcpConfig(services);
302
+ }
303
+
304
+ async function createMcpConfig(services: ServiceType[]): Promise<void> {
305
+ const mcpConfig: Record<string, unknown> = {
306
+ mcpServers: {
307
+ // Immer Firecrawl für Recherche
308
+ firecrawl: {
309
+ command: 'npx',
310
+ args: ['-y', 'firecrawl-mcp'],
311
+ env: { FIRECRAWL_API_KEY: '${FIRECRAWL_API_KEY}' },
312
+ },
313
+ // Immer Playwright für Frontend-Tests
314
+ playwright: {
315
+ command: 'npx',
316
+ args: ['-y', '@anthropic/mcp-playwright'],
317
+ },
318
+ },
319
+ };
320
+
321
+ const servers = mcpConfig.mcpServers as Record<string, unknown>;
322
+
323
+ if (services.includes('supabase')) {
324
+ servers.supabase = {
325
+ command: 'npx',
326
+ args: ['-y', '@supabase/mcp-server-supabase'],
327
+ env: {
328
+ SUPABASE_URL: '${SUPABASE_URL}',
329
+ SUPABASE_SECRET_KEY: '${SUPABASE_SECRET_KEY}',
330
+ },
331
+ };
332
+ }
333
+
334
+ if (services.includes('n8n')) {
335
+ servers['n8n-mcp'] = {
336
+ command: 'npx',
337
+ args: ['n8n-mcp'],
338
+ env: {
339
+ MCP_MODE: 'stdio',
340
+ LOG_LEVEL: 'error',
341
+ DISABLE_CONSOLE_OUTPUT: 'true',
342
+ N8N_API_URL: '${N8N_URL}',
343
+ N8N_API_KEY: '${N8N_API_KEY}',
344
+ },
345
+ };
346
+ }
347
+
348
+ await fs.writeJson('.mcp.json', mcpConfig, { spaces: 2 });
349
+ }
350
+
351
+ function getPluginsForServices(services: ServiceType[]): string[] {
352
+ const plugins = ['researcher', 'plan-agent', 'frontend-test'];
353
+
354
+ if (services.includes('supabase')) {
355
+ plugins.push('supabase-agent');
356
+ }
357
+
358
+ if (services.includes('n8n')) {
359
+ plugins.push('n8n-agent');
360
+ }
361
+
362
+ return plugins;
363
+ }
364
+
365
+ function showNextSteps(repoName: string, services: ServiceType[]): void {
366
+ logger.blank();
367
+ logger.title('Nächste Schritte');
368
+
369
+ logger.info('1. Konfiguriere deine API Keys:');
370
+ logger.dim(' Bearbeite .claude/settings.local.json');
371
+ logger.blank();
372
+
373
+ if (services.length > 0) {
374
+ logger.info('2. Authentifiziere dich bei den Services:');
375
+ if (services.includes('supabase')) {
376
+ logger.dim(' $ supabase login');
377
+ }
378
+ if (services.includes('n8n')) {
379
+ logger.dim(' $ cl auth login n8n');
380
+ }
381
+ logger.blank();
382
+ }
383
+
384
+ logger.info(`${services.length > 0 ? '3' : '2'}. Synchronisiere mit GitHub:`);
385
+ logger.dim(` $ cl sync`);
386
+ logger.dim(` (Erstellt Clevermation/${repoName})`);
387
+ logger.blank();
388
+
389
+ logger.info(`${services.length > 0 ? '4' : '3'}. Starte Claude Code:`);
390
+ logger.dim(' $ claude');
391
+ logger.blank();
392
+ }
393
+
394
+ /**
395
+ * Wendet optimale Projekt-Einstellungen an.
396
+ * Erstellt/aktualisiert globale CLAUDE.md mit Clevermation-Kontext.
397
+ * TODO: Wird in separater Session mit User definiert.
398
+ */
399
+ async function applyOptimalSettings(): Promise<void> {
400
+ const globalClaudeMd = path.join(os.homedir(), '.claude', 'CLAUDE.md');
401
+
402
+ // Prüfe ob globale CLAUDE.md bereits existiert
403
+ const exists = await fs.pathExists(globalClaudeMd);
404
+
405
+ if (!exists) {
406
+ // Erstelle Basis CLAUDE.md (wird später mit User verfeinert)
407
+ await fs.ensureDir(path.dirname(globalClaudeMd));
408
+
409
+ const claudeMdContent = `# Clevermation Entwickler-Kontext
410
+
411
+ ## Unternehmen
412
+ Clevermation - No-Code/Low-Code Automation Agentur
413
+
414
+ ## Package Manager Standards
415
+ - TypeScript/JavaScript: \`bun\` (nicht npm/yarn/pnpm)
416
+ - Python: \`uv\` (nicht pip/poetry)
417
+
418
+ ## Projekt-Konventionen
419
+ - Deutsche Kommentare und Dokumentation
420
+ - TypeScript mit strict mode
421
+ - Formatierung: Prettier (TS/JS), Ruff (Python)
422
+
423
+ ---
424
+ *Erstellt von Clevermation CLI - Weitere Einstellungen folgen*
425
+ `;
426
+
427
+ await fs.writeFile(globalClaudeMd, claudeMdContent);
428
+ }
429
+ }
@@ -0,0 +1,104 @@
1
+ import { Command } from 'commander';
2
+ import { select } from '@inquirer/prompts';
3
+ import { execa } from 'execa';
4
+ import { logger } from '../utils/logger.js';
5
+ import { loadGlobalConfig, saveGlobalConfig } from '../utils/config.js';
6
+
7
+ type IDE = 'code' | 'cursor' | 'webstorm' | 'zed';
8
+
9
+ const IDE_COMMANDS: Record<IDE, { command: string; name: string }> = {
10
+ code: { command: 'code', name: 'VS Code' },
11
+ cursor: { command: 'cursor', name: 'Cursor' },
12
+ webstorm: { command: 'webstorm', name: 'WebStorm' },
13
+ zed: { command: 'zed', name: 'Zed' },
14
+ };
15
+
16
+ export function createOpenCommand(): Command {
17
+ const cmd = new Command('open').description('Öffne Projekt in IDE');
18
+
19
+ cmd
20
+ .argument('[path]', 'Pfad zum Öffnen', '.')
21
+ .option('-i, --ide <ide>', 'IDE (code, cursor, webstorm, zed)')
22
+ .option('--set-default <ide>', 'Setze Standard-IDE')
23
+ .action(async (path: string, options: { ide?: string; setDefault?: string }) => {
24
+ if (options.setDefault) {
25
+ await setDefaultIDE(options.setDefault as IDE);
26
+ return;
27
+ }
28
+
29
+ await openInIDE(path, options.ide as IDE | undefined);
30
+ });
31
+
32
+ return cmd;
33
+ }
34
+
35
+ async function openInIDE(path: string, ide?: IDE): Promise<void> {
36
+ // IDE ermitteln (Argument > Config > Default)
37
+ let selectedIDE = ide;
38
+
39
+ if (!selectedIDE) {
40
+ const config = await loadGlobalConfig();
41
+ selectedIDE = config?.preferences?.defaultIDE as IDE | undefined;
42
+ }
43
+
44
+ // Wenn immer noch keine IDE, VS Code als Default
45
+ if (!selectedIDE) {
46
+ selectedIDE = 'code';
47
+ }
48
+
49
+ const ideConfig = IDE_COMMANDS[selectedIDE];
50
+
51
+ if (!ideConfig) {
52
+ logger.error(`Unbekannte IDE: ${selectedIDE}`);
53
+ logger.dim(' Verfügbar: code, cursor, webstorm, zed');
54
+ return;
55
+ }
56
+
57
+ // Prüfe ob IDE installiert ist
58
+ try {
59
+ await execa('which', [ideConfig.command]);
60
+ } catch {
61
+ logger.error(`${ideConfig.name} nicht gefunden`);
62
+ logger.dim(` Stelle sicher, dass "${ideConfig.command}" im PATH ist.`);
63
+
64
+ // Frage nach Alternative
65
+ const alternative = (await select({
66
+ message: 'Alternative IDE wählen?',
67
+ choices: Object.entries(IDE_COMMANDS).map(([key, value]) => ({
68
+ name: value.name,
69
+ value: key,
70
+ })),
71
+ })) as IDE;
72
+
73
+ selectedIDE = alternative;
74
+ }
75
+
76
+ // Öffne IDE
77
+ try {
78
+ const finalConfig = IDE_COMMANDS[selectedIDE];
79
+ await execa(finalConfig.command, [path], { stdio: 'ignore', detached: true });
80
+ logger.success(`Geöffnet in ${finalConfig.name}`);
81
+ } catch (error) {
82
+ logger.error('Konnte IDE nicht öffnen');
83
+ }
84
+ }
85
+
86
+ async function setDefaultIDE(ide: IDE): Promise<void> {
87
+ if (!IDE_COMMANDS[ide]) {
88
+ logger.error(`Unbekannte IDE: ${ide}`);
89
+ logger.dim(' Verfügbar: code, cursor, webstorm, zed');
90
+ return;
91
+ }
92
+
93
+ const config = (await loadGlobalConfig()) || {
94
+ version: '1.0.0',
95
+ auth: {},
96
+ preferences: {},
97
+ };
98
+
99
+ config.preferences = config.preferences || {};
100
+ config.preferences.defaultIDE = ide;
101
+
102
+ await saveGlobalConfig(config);
103
+ logger.success(`Standard-IDE: ${IDE_COMMANDS[ide].name}`);
104
+ }
@@ -0,0 +1,181 @@
1
+ import { Command } from 'commander';
2
+ import { confirm, input } from '@inquirer/prompts';
3
+ import { execa } from 'execa';
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+ import { logger } from '../utils/logger.js';
7
+ import { loadProjectConfig } from '../utils/config.js';
8
+
9
+ interface SyncOptions {
10
+ force?: boolean;
11
+ }
12
+
13
+ export function createSyncCommand(): Command {
14
+ return new Command('sync')
15
+ .description('Synchronisiere Projekt mit GitHub')
16
+ .option('-f, --force', 'Force push (mit Vorsicht verwenden)')
17
+ .action(async (options: SyncOptions) => {
18
+ await runSync(options);
19
+ });
20
+ }
21
+
22
+ async function runSync(options: SyncOptions) {
23
+ logger.title('GitHub Synchronisation');
24
+
25
+ // Step 1: Prüfe gh CLI Installation
26
+ const ghInstalled = await checkGhCli();
27
+ if (!ghInstalled) {
28
+ logger.error('GitHub CLI (gh) ist nicht installiert.');
29
+ logger.info('Installiere mit: brew install gh');
30
+ return;
31
+ }
32
+
33
+ // Step 2: Prüfe gh Authentifizierung
34
+ const ghAuthenticated = await checkGhAuth();
35
+ if (!ghAuthenticated) {
36
+ logger.warning('GitHub CLI ist nicht authentifiziert.');
37
+ const authenticate = await confirm({
38
+ message: 'Möchtest du dich jetzt authentifizieren?',
39
+ default: true,
40
+ });
41
+
42
+ if (authenticate) {
43
+ logger.blank();
44
+ await execa('gh', ['auth', 'login'], { stdio: 'inherit' });
45
+ logger.blank();
46
+
47
+ // Erneut prüfen
48
+ if (!(await checkGhAuth())) {
49
+ logger.error('Authentifizierung fehlgeschlagen.');
50
+ return;
51
+ }
52
+ } else {
53
+ return;
54
+ }
55
+ }
56
+
57
+ // Step 3: Lade Projekt Config
58
+ const config = await loadProjectConfig();
59
+ if (!config) {
60
+ logger.error('Kein Clevermation Projekt gefunden.');
61
+ logger.info('Führe zuerst "cl init" aus.');
62
+ return;
63
+ }
64
+
65
+ const repoName = config.services.github.repo;
66
+ const org = config.services.github.org;
67
+ const fullRepoName = `${org}/${repoName}`;
68
+
69
+ // Step 4: Prüfe ob Remote existiert
70
+ const hasRemote = await checkRemoteExists();
71
+
72
+ if (!hasRemote) {
73
+ // Prüfe ob Repo auf GitHub existiert
74
+ const repoExists = await checkRepoExists(org, repoName);
75
+
76
+ if (!repoExists) {
77
+ // Repo erstellen
78
+ const createRepo = await confirm({
79
+ message: `Privates Repository ${fullRepoName} erstellen?`,
80
+ default: true,
81
+ });
82
+
83
+ if (createRepo) {
84
+ const spinner = ora('Erstelle Repository...').start();
85
+ try {
86
+ await execa('gh', [
87
+ 'repo',
88
+ 'create',
89
+ fullRepoName,
90
+ '--private',
91
+ '--source',
92
+ '.',
93
+ '--remote',
94
+ 'origin',
95
+ '--push',
96
+ ]);
97
+ spinner.succeed(`Repository erstellt: ${fullRepoName}`);
98
+ logger.success('Projekt synchronisiert!');
99
+ return;
100
+ } catch (error) {
101
+ spinner.fail('Repository-Erstellung fehlgeschlagen');
102
+ throw error;
103
+ }
104
+ } else {
105
+ return;
106
+ }
107
+ } else {
108
+ // Repo existiert, aber kein Remote - Remote hinzufügen
109
+ const spinner = ora('Füge Remote hinzu...').start();
110
+ await execa('git', ['remote', 'add', 'origin', `git@github.com:${fullRepoName}.git`]);
111
+ spinner.succeed('Remote hinzugefügt');
112
+ }
113
+ }
114
+
115
+ // Step 5: Push zu Remote
116
+ const spinner = ora('Pushe zu Remote...').start();
117
+ try {
118
+ const pushArgs = ['push', '-u', 'origin', 'main'];
119
+ if (options.force) {
120
+ pushArgs.push('--force');
121
+ }
122
+ await execa('git', pushArgs);
123
+ spinner.succeed('Mit GitHub synchronisiert!');
124
+ } catch (error) {
125
+ spinner.fail('Push fehlgeschlagen');
126
+
127
+ // Prüfen ob es an fehlendem Commit liegt
128
+ const hasCommits = await checkHasCommits();
129
+ if (!hasCommits) {
130
+ logger.info('Tipp: Du musst erst einen Commit erstellen.');
131
+ logger.dim(' git add . && git commit -m "Initial commit"');
132
+ } else {
133
+ throw error;
134
+ }
135
+ }
136
+ }
137
+
138
+ async function checkGhCli(): Promise<boolean> {
139
+ try {
140
+ await execa('gh', ['--version']);
141
+ return true;
142
+ } catch {
143
+ return false;
144
+ }
145
+ }
146
+
147
+ async function checkGhAuth(): Promise<boolean> {
148
+ try {
149
+ await execa('gh', ['auth', 'status']);
150
+ return true;
151
+ } catch {
152
+ return false;
153
+ }
154
+ }
155
+
156
+ async function checkRemoteExists(): Promise<boolean> {
157
+ try {
158
+ const result = await execa('git', ['remote', 'get-url', 'origin']);
159
+ return result.stdout.length > 0;
160
+ } catch {
161
+ return false;
162
+ }
163
+ }
164
+
165
+ async function checkRepoExists(org: string, repo: string): Promise<boolean> {
166
+ try {
167
+ await execa('gh', ['repo', 'view', `${org}/${repo}`]);
168
+ return true;
169
+ } catch {
170
+ return false;
171
+ }
172
+ }
173
+
174
+ async function checkHasCommits(): Promise<boolean> {
175
+ try {
176
+ await execa('git', ['rev-parse', 'HEAD']);
177
+ return true;
178
+ } catch {
179
+ return false;
180
+ }
181
+ }