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,302 @@
1
+ import { Command } from 'commander';
2
+ import { select, confirm, input, password } from '@inquirer/prompts';
3
+ import { execa } from 'execa';
4
+ import ora from 'ora';
5
+ import chalk from 'chalk';
6
+ import { logger } from '../utils/logger.js';
7
+ import {
8
+ checkGitHubAuth,
9
+ checkSupabaseAuth,
10
+ } from '../utils/prerequisites.js';
11
+ import { loadGlobalConfig, saveGlobalConfig } from '../utils/config.js';
12
+
13
+ type ServiceType = 'github' | 'supabase' | 'n8n' | 'elevenlabs' | 'all';
14
+
15
+ export function createAuthCommand(): Command {
16
+ const cmd = new Command('auth').description('Authentifizierung für Services verwalten');
17
+
18
+ cmd
19
+ .command('status')
20
+ .description('Zeige Auth-Status aller Services')
21
+ .action(async () => {
22
+ await showAuthStatus();
23
+ });
24
+
25
+ cmd
26
+ .command('login [service]')
27
+ .description('Bei einem Service anmelden')
28
+ .action(async (service?: string) => {
29
+ await loginService(service as ServiceType);
30
+ });
31
+
32
+ cmd
33
+ .command('logout [service]')
34
+ .description('Von einem Service abmelden')
35
+ .action(async (service?: string) => {
36
+ await logoutService(service as ServiceType);
37
+ });
38
+
39
+ // Default action wenn nur `cl auth` aufgerufen wird
40
+ cmd.action(async () => {
41
+ await showAuthStatus();
42
+ });
43
+
44
+ return cmd;
45
+ }
46
+
47
+ async function showAuthStatus(): Promise<void> {
48
+ logger.title('Authentifizierung');
49
+
50
+ const spinner = ora('Prüfe Auth-Status...').start();
51
+
52
+ // GitHub
53
+ const ghAuth = await checkGitHubAuth();
54
+
55
+ // Supabase
56
+ const supabaseAuth = await checkSupabaseAuth();
57
+
58
+ // N8N & ElevenLabs aus Config
59
+ const config = await loadGlobalConfig();
60
+
61
+ spinner.stop();
62
+
63
+ console.log(chalk.bold(' Services:\n'));
64
+
65
+ // GitHub
66
+ if (ghAuth.authenticated) {
67
+ console.log(chalk.green(' ✓'), 'GitHub', chalk.dim(`(@${ghAuth.username})`));
68
+ } else {
69
+ console.log(chalk.red(' ✗'), 'GitHub', chalk.dim('(nicht angemeldet)'));
70
+ }
71
+
72
+ // Supabase
73
+ if (supabaseAuth) {
74
+ console.log(chalk.green(' ✓'), 'Supabase');
75
+ } else {
76
+ console.log(chalk.yellow(' ○'), 'Supabase', chalk.dim('(nicht angemeldet)'));
77
+ }
78
+
79
+ // N8N
80
+ if (config?.auth?.n8n?.apiKey) {
81
+ console.log(chalk.green(' ✓'), 'N8N', chalk.dim(`(${config.auth.n8n.url})`));
82
+ } else {
83
+ console.log(chalk.yellow(' ○'), 'N8N', chalk.dim('(nicht konfiguriert)'));
84
+ }
85
+
86
+ // ElevenLabs
87
+ if (config?.auth?.elevenlabs?.apiKey) {
88
+ console.log(chalk.green(' ✓'), 'ElevenLabs');
89
+ } else {
90
+ console.log(chalk.yellow(' ○'), 'ElevenLabs', chalk.dim('(nicht konfiguriert)'));
91
+ }
92
+
93
+ logger.blank();
94
+ console.log(chalk.dim(' Anmelden: cl auth login <service>'));
95
+ console.log(chalk.dim(' Services: github, supabase, n8n, elevenlabs, all'));
96
+ logger.blank();
97
+ }
98
+
99
+ async function loginService(service?: ServiceType): Promise<void> {
100
+ // Wenn kein Service angegeben, fragen
101
+ const selectedService =
102
+ service ||
103
+ ((await select({
104
+ message: 'Bei welchem Service anmelden?',
105
+ choices: [
106
+ { name: 'Alle Services', value: 'all' },
107
+ { name: 'GitHub', value: 'github' },
108
+ { name: 'Supabase', value: 'supabase' },
109
+ { name: 'N8N', value: 'n8n' },
110
+ { name: 'ElevenLabs', value: 'elevenlabs' },
111
+ ],
112
+ })) as ServiceType);
113
+
114
+ if (selectedService === 'all') {
115
+ await loginGitHub();
116
+ await loginSupabase();
117
+ await loginN8N();
118
+ await loginElevenLabs();
119
+ } else {
120
+ switch (selectedService) {
121
+ case 'github':
122
+ await loginGitHub();
123
+ break;
124
+ case 'supabase':
125
+ await loginSupabase();
126
+ break;
127
+ case 'n8n':
128
+ await loginN8N();
129
+ break;
130
+ case 'elevenlabs':
131
+ await loginElevenLabs();
132
+ break;
133
+ }
134
+ }
135
+ }
136
+
137
+ async function loginGitHub(): Promise<void> {
138
+ logger.info('GitHub Login');
139
+
140
+ const ghAuth = await checkGitHubAuth();
141
+ if (ghAuth.authenticated) {
142
+ logger.success(`Bereits angemeldet als @${ghAuth.username}`);
143
+
144
+ const reauth = await confirm({
145
+ message: 'Erneut anmelden?',
146
+ default: false,
147
+ });
148
+
149
+ if (!reauth) return;
150
+ }
151
+
152
+ try {
153
+ await execa('gh', ['auth', 'login', '--web'], { stdio: 'inherit' });
154
+
155
+ // Setup git für GitHub Packages
156
+ await execa('gh', ['auth', 'setup-git']);
157
+
158
+ logger.success('GitHub Login erfolgreich');
159
+ } catch {
160
+ logger.error('GitHub Login fehlgeschlagen');
161
+ }
162
+ }
163
+
164
+ async function loginSupabase(): Promise<void> {
165
+ logger.info('Supabase Login');
166
+
167
+ // Prüfe ob Supabase CLI installiert
168
+ try {
169
+ await execa('supabase', ['--version']);
170
+ } catch {
171
+ logger.warning('Supabase CLI nicht installiert');
172
+ logger.dim(' Installieren: brew install supabase/tap/supabase');
173
+ return;
174
+ }
175
+
176
+ try {
177
+ await execa('supabase', ['login'], { stdio: 'inherit' });
178
+ logger.success('Supabase Login erfolgreich');
179
+ } catch {
180
+ logger.error('Supabase Login fehlgeschlagen');
181
+ }
182
+ }
183
+
184
+ async function loginN8N(): Promise<void> {
185
+ logger.info('N8N Konfiguration');
186
+
187
+ const url = await input({
188
+ message: 'N8N Instance URL',
189
+ default: 'https://n8n.clevermation.com',
190
+ validate: (v) => v.startsWith('http') || 'Muss eine gültige URL sein',
191
+ });
192
+
193
+ const apiKey = await password({
194
+ message: 'N8N API Key',
195
+ validate: (v) => v.length > 0 || 'API Key erforderlich',
196
+ });
197
+
198
+ // Validiere Credentials
199
+ const spinner = ora('Validiere N8N Credentials...').start();
200
+
201
+ try {
202
+ const response = await fetch(`${url}/api/v1/workflows`, {
203
+ headers: { 'X-N8N-API-KEY': apiKey },
204
+ signal: AbortSignal.timeout(10000),
205
+ });
206
+
207
+ if (response.ok) {
208
+ spinner.succeed('N8N Credentials gültig');
209
+
210
+ // Speichere in Config
211
+ const config = (await loadGlobalConfig()) || {
212
+ version: '1.0.0',
213
+ auth: {},
214
+ preferences: {},
215
+ };
216
+
217
+ config.auth = config.auth || {};
218
+ config.auth.n8n = { url, apiKey };
219
+
220
+ await saveGlobalConfig(config);
221
+ logger.success('N8N konfiguriert');
222
+ } else {
223
+ spinner.fail('Ungültige N8N Credentials');
224
+ }
225
+ } catch {
226
+ spinner.fail('Verbindung zu N8N fehlgeschlagen');
227
+ }
228
+ }
229
+
230
+ async function loginElevenLabs(): Promise<void> {
231
+ logger.info('ElevenLabs Konfiguration');
232
+
233
+ const apiKey = await password({
234
+ message: 'ElevenLabs API Key',
235
+ validate: (v) => v.length > 0 || 'API Key erforderlich',
236
+ });
237
+
238
+ // Validiere API Key
239
+ const spinner = ora('Validiere ElevenLabs API Key...').start();
240
+
241
+ try {
242
+ const response = await fetch('https://api.elevenlabs.io/v1/user', {
243
+ headers: { 'xi-api-key': apiKey },
244
+ signal: AbortSignal.timeout(10000),
245
+ });
246
+
247
+ if (response.ok) {
248
+ spinner.succeed('ElevenLabs API Key gültig');
249
+
250
+ // Speichere in Config
251
+ const config = (await loadGlobalConfig()) || {
252
+ version: '1.0.0',
253
+ auth: {},
254
+ preferences: {},
255
+ };
256
+
257
+ config.auth = config.auth || {};
258
+ config.auth.elevenlabs = { apiKey };
259
+
260
+ await saveGlobalConfig(config);
261
+ logger.success('ElevenLabs konfiguriert');
262
+ } else {
263
+ spinner.fail('Ungültiger ElevenLabs API Key');
264
+ }
265
+ } catch {
266
+ spinner.fail('Validierung fehlgeschlagen');
267
+ }
268
+ }
269
+
270
+ async function logoutService(service?: ServiceType): Promise<void> {
271
+ const selectedService =
272
+ service ||
273
+ ((await select({
274
+ message: 'Von welchem Service abmelden?',
275
+ choices: [
276
+ { name: 'GitHub', value: 'github' },
277
+ { name: 'Supabase', value: 'supabase' },
278
+ { name: 'N8N', value: 'n8n' },
279
+ { name: 'ElevenLabs', value: 'elevenlabs' },
280
+ ],
281
+ })) as ServiceType);
282
+
283
+ switch (selectedService) {
284
+ case 'github':
285
+ await execa('gh', ['auth', 'logout'], { stdio: 'inherit' });
286
+ break;
287
+ case 'supabase':
288
+ // Supabase hat kein logout command
289
+ logger.info('Supabase: Lösche Token manuell in ~/.supabase');
290
+ break;
291
+ case 'n8n':
292
+ case 'elevenlabs': {
293
+ const config = await loadGlobalConfig();
294
+ if (config?.auth) {
295
+ delete config.auth[selectedService];
296
+ await saveGlobalConfig(config);
297
+ logger.success(`${selectedService} Credentials entfernt`);
298
+ }
299
+ break;
300
+ }
301
+ }
302
+ }
@@ -0,0 +1,174 @@
1
+ import { Command } from 'commander';
2
+ import { select, input, confirm } from '@inquirer/prompts';
3
+ import chalk from 'chalk';
4
+ import { logger } from '../utils/logger.js';
5
+ import {
6
+ loadProjectConfig,
7
+ loadGlobalConfig,
8
+ saveGlobalConfig,
9
+ } from '../utils/config.js';
10
+
11
+ export function createConfigCommand(): Command {
12
+ const cmd = new Command('config').description(
13
+ 'Zeige und bearbeite Projektkonfiguration'
14
+ );
15
+
16
+ cmd
17
+ .command('show')
18
+ .description('Zeige aktuelle Konfiguration')
19
+ .action(async () => {
20
+ await showConfig();
21
+ });
22
+
23
+ cmd
24
+ .command('auth')
25
+ .description('Verwalte Service-Authentifizierung')
26
+ .action(async () => {
27
+ await manageAuth();
28
+ });
29
+
30
+ // Default: show
31
+ cmd.action(async () => {
32
+ await showConfig();
33
+ });
34
+
35
+ return cmd;
36
+ }
37
+
38
+ async function showConfig() {
39
+ const projectConfig = await loadProjectConfig();
40
+ const globalConfig = await loadGlobalConfig();
41
+
42
+ logger.title('Konfiguration');
43
+
44
+ if (projectConfig) {
45
+ logger.info('Projekt:');
46
+ logger.dim(` Name: ${projectConfig.project.name}`);
47
+ logger.dim(` Typ: ${projectConfig.project.type}`);
48
+ if (projectConfig.project.customer) {
49
+ logger.dim(` Kunde: ${projectConfig.project.customer}`);
50
+ }
51
+ logger.dim(` Repo: ${projectConfig.services.github.org}/${projectConfig.services.github.repo}`);
52
+ logger.blank();
53
+
54
+ logger.info('Services:');
55
+ logger.dim(` GitHub: ✓ aktiviert`);
56
+ logger.dim(
57
+ ` Supabase: ${projectConfig.services.supabase?.enabled ? '✓ aktiviert' : '✗ deaktiviert'}`
58
+ );
59
+ logger.dim(
60
+ ` N8N: ${projectConfig.services.n8n?.enabled ? '✓ aktiviert' : '✗ deaktiviert'}`
61
+ );
62
+ logger.dim(
63
+ ` ElevenLabs: ${projectConfig.services.elevenlabs?.enabled ? '✓ aktiviert' : '✗ deaktiviert'}`
64
+ );
65
+ logger.blank();
66
+
67
+ logger.info('Claude Code:');
68
+ logger.dim(` Model: ${projectConfig.claudeCode.model}`);
69
+ logger.dim(` Plugins: ${projectConfig.claudeCode.plugins.join(', ')}`);
70
+ } else {
71
+ logger.warning('Kein Projekt gefunden. Führe "cl init" aus.');
72
+ }
73
+
74
+ logger.blank();
75
+ logger.info('Global:');
76
+ logger.dim(` Standard Model: ${globalConfig.preferences.defaultModel}`);
77
+ logger.dim(` Auto-Update: ${globalConfig.preferences.autoUpdate ? 'Ja' : 'Nein'}`);
78
+ logger.blank();
79
+ }
80
+
81
+ async function manageAuth() {
82
+ logger.title('Service Authentifizierung');
83
+
84
+ const service = await select({
85
+ message: 'Wähle Service zur Konfiguration',
86
+ choices: [
87
+ { name: 'N8N', value: 'n8n' },
88
+ { name: 'ElevenLabs', value: 'elevenlabs' },
89
+ { name: 'Supabase (via CLI)', value: 'supabase' },
90
+ { name: 'GitHub (via CLI)', value: 'github' },
91
+ ],
92
+ });
93
+
94
+ const globalConfig = await loadGlobalConfig();
95
+
96
+ switch (service) {
97
+ case 'n8n':
98
+ await authN8n(globalConfig);
99
+ break;
100
+ case 'elevenlabs':
101
+ await authElevenLabs(globalConfig);
102
+ break;
103
+ case 'supabase':
104
+ logger.info('Supabase verwendet die Supabase CLI zur Authentifizierung.');
105
+ logger.dim(' Führe aus: supabase login');
106
+ break;
107
+ case 'github':
108
+ logger.info('GitHub verwendet die GitHub CLI zur Authentifizierung.');
109
+ logger.dim(' Führe aus: gh auth login');
110
+ break;
111
+ }
112
+ }
113
+
114
+ async function authN8n(globalConfig: Awaited<ReturnType<typeof loadGlobalConfig>>) {
115
+ const url = await input({
116
+ message: 'N8N Instance URL',
117
+ default: globalConfig.auth.n8n?.url || 'https://n8n.clevermation.com',
118
+ validate: (v) => v.startsWith('http') || 'Muss eine gültige URL sein',
119
+ });
120
+
121
+ const apiKey = await input({
122
+ message: 'N8N API Key',
123
+ validate: (v) => v.length > 0 || 'API Key erforderlich',
124
+ });
125
+
126
+ // API Key validieren
127
+ logger.info('Validiere N8N Credentials...');
128
+ try {
129
+ const response = await fetch(`${url}/api/v1/workflows`, {
130
+ headers: { 'X-N8N-API-KEY': apiKey },
131
+ });
132
+
133
+ if (response.ok) {
134
+ logger.success('N8N Credentials gültig!');
135
+
136
+ // Speichern
137
+ globalConfig.auth.n8n = { url, apiKey };
138
+ await saveGlobalConfig(globalConfig);
139
+ logger.success('Credentials gespeichert.');
140
+ } else {
141
+ logger.error('Ungültige N8N Credentials.');
142
+ }
143
+ } catch {
144
+ logger.error('Verbindung zu N8N fehlgeschlagen.');
145
+ }
146
+ }
147
+
148
+ async function authElevenLabs(globalConfig: Awaited<ReturnType<typeof loadGlobalConfig>>) {
149
+ const apiKey = await input({
150
+ message: 'ElevenLabs API Key',
151
+ validate: (v) => v.length > 0 || 'API Key erforderlich',
152
+ });
153
+
154
+ // API Key validieren
155
+ logger.info('Validiere ElevenLabs API Key...');
156
+ try {
157
+ const response = await fetch('https://api.elevenlabs.io/v1/user', {
158
+ headers: { 'xi-api-key': apiKey },
159
+ });
160
+
161
+ if (response.ok) {
162
+ logger.success('ElevenLabs API Key gültig!');
163
+
164
+ // Speichern
165
+ globalConfig.auth.elevenlabs = { apiKey };
166
+ await saveGlobalConfig(globalConfig);
167
+ logger.success('API Key gespeichert.');
168
+ } else {
169
+ logger.error('Ungültiger ElevenLabs API Key.');
170
+ }
171
+ } catch {
172
+ logger.error('Validierung fehlgeschlagen.');
173
+ }
174
+ }
@@ -0,0 +1,15 @@
1
+ import { Command } from 'commander';
2
+ import { showPrerequisiteStatus } from '../utils/prerequisites.js';
3
+ import { logger } from '../utils/logger.js';
4
+
5
+ export function createDoctorCommand(): Command {
6
+ return new Command('doctor')
7
+ .description('Prüfe System-Voraussetzungen und zeige Probleme')
8
+ .action(async () => {
9
+ await showPrerequisiteStatus();
10
+
11
+ logger.blank();
12
+ logger.dim(' Bei Problemen: cl auth login');
13
+ logger.blank();
14
+ });
15
+ }
@@ -0,0 +1,113 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import fs from 'fs-extra';
4
+ import path from 'path';
5
+ import { logger } from '../utils/logger.js';
6
+ import { loadProjectConfig } from '../utils/config.js';
7
+
8
+ interface ExplainOptions {
9
+ verbose?: boolean;
10
+ }
11
+
12
+ export function createExplainCommand(): Command {
13
+ return new Command('explain')
14
+ .description('Erkläre die aktuelle Projektstruktur')
15
+ .option('-v, --verbose', 'Zeige detaillierte Erklärung')
16
+ .action(async (options: ExplainOptions) => {
17
+ await explainProject(options.verbose ?? false);
18
+ });
19
+ }
20
+
21
+ async function explainProject(verbose: boolean) {
22
+ const config = await loadProjectConfig();
23
+
24
+ logger.title('Projekt Übersicht');
25
+
26
+ if (!config) {
27
+ logger.error('Kein Clevermation Projekt gefunden.');
28
+ logger.info('Führe "cl init" aus, um ein Projekt zu erstellen.');
29
+ return;
30
+ }
31
+
32
+ // Basis-Info
33
+ logger.info('Projekt:');
34
+ logger.dim(` Name: ${config.project.name}`);
35
+ logger.dim(
36
+ ` Typ: ${config.project.type === 'customer' ? 'Kundenprojekt' : 'Internes Projekt'}`
37
+ );
38
+ if (config.project.customer) {
39
+ logger.dim(` Kunde: ${config.project.customer}`);
40
+ }
41
+ logger.blank();
42
+
43
+ // GitHub
44
+ logger.info('GitHub:');
45
+ logger.dim(` Repository: ${config.services.github.org}/${config.services.github.repo}`);
46
+ logger.dim(` Sichtbarkeit: Privat`);
47
+ logger.blank();
48
+
49
+ // Services
50
+ const enabledServices: string[] = ['GitHub'];
51
+ if (config.services.supabase?.enabled) enabledServices.push('Supabase');
52
+ if (config.services.n8n?.enabled) enabledServices.push('N8N');
53
+ if (config.services.elevenlabs?.enabled) enabledServices.push('ElevenLabs');
54
+
55
+ logger.info('Aktivierte Services:');
56
+ for (const service of enabledServices) {
57
+ logger.dim(` • ${service}`);
58
+ }
59
+ logger.blank();
60
+
61
+ // Claude Code
62
+ logger.info('Claude Code:');
63
+ logger.dim(` Standard Model: ${config.claudeCode.model}`);
64
+ logger.dim(` Marketplace: ${config.claudeCode.marketplace}`);
65
+ logger.blank();
66
+
67
+ logger.info('Installierte Plugins:');
68
+ for (const plugin of config.claudeCode.plugins) {
69
+ logger.dim(` • ${plugin}`);
70
+ }
71
+ logger.blank();
72
+
73
+ // Verbose: Verzeichnisstruktur
74
+ if (verbose) {
75
+ logger.info('Verzeichnisstruktur:');
76
+ await showDirectoryTree();
77
+ logger.blank();
78
+ }
79
+
80
+ // Timestamps
81
+ logger.dim(`Erstellt: ${new Date(config.createdAt).toLocaleDateString('de-DE')}`);
82
+ logger.dim(`Aktualisiert: ${new Date(config.updatedAt).toLocaleDateString('de-DE')}`);
83
+ logger.blank();
84
+ }
85
+
86
+ async function showDirectoryTree(dir: string = '.', prefix: string = ' '): Promise<void> {
87
+ const ignoreDirs = ['node_modules', '.git', 'dist', '.bun'];
88
+
89
+ try {
90
+ const entries = await fs.readdir(dir, { withFileTypes: true });
91
+ const filteredEntries = entries.filter((e) => !ignoreDirs.includes(e.name));
92
+
93
+ for (let i = 0; i < filteredEntries.length; i++) {
94
+ const entry = filteredEntries[i];
95
+ if (!entry) continue;
96
+
97
+ const isLast = i === filteredEntries.length - 1;
98
+ const connector = isLast ? '└── ' : '├── ';
99
+
100
+ if (entry.isDirectory()) {
101
+ console.log(chalk.dim(`${prefix}${connector}${chalk.blue(entry.name)}/`));
102
+ await showDirectoryTree(
103
+ path.join(dir, entry.name),
104
+ prefix + (isLast ? ' ' : '│ ')
105
+ );
106
+ } else {
107
+ console.log(chalk.dim(`${prefix}${connector}${entry.name}`));
108
+ }
109
+ }
110
+ } catch {
111
+ // Ignoriere Fehler
112
+ }
113
+ }