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,90 @@
1
+ import { Command } from 'commander';
2
+ import { confirm } from '@inquirer/prompts';
3
+ import { execa } from 'execa';
4
+ import ora from 'ora';
5
+ import { logger } from '../utils/logger.js';
6
+
7
+ interface UpdateOptions {
8
+ cliOnly?: boolean;
9
+ pluginsOnly?: boolean;
10
+ }
11
+
12
+ export function createUpdateCommand(): Command {
13
+ return new Command('update')
14
+ .description('Aktualisiere CLI und Plugins')
15
+ .option('--cli-only', 'Nur CLI aktualisieren')
16
+ .option('--plugins-only', 'Nur Plugins aktualisieren')
17
+ .action(async (options: UpdateOptions) => {
18
+ await runUpdate(options);
19
+ });
20
+ }
21
+
22
+ async function runUpdate(options: UpdateOptions) {
23
+ logger.title('Update');
24
+
25
+ // CLI Update
26
+ if (!options.pluginsOnly) {
27
+ const spinner = ora('Prüfe auf CLI Updates...').start();
28
+
29
+ try {
30
+ // Aktuelle Version ermitteln
31
+ const packageJson = await import('../../package.json', {
32
+ assert: { type: 'json' },
33
+ });
34
+ const currentVersion = packageJson.default.version;
35
+
36
+ spinner.text = 'Aktualisiere CLI...';
37
+
38
+ // Update durchführen
39
+ await execa('bun', ['update', 'clevermation-cli']);
40
+
41
+ spinner.succeed(`CLI aktualisiert (v${currentVersion})`);
42
+ } catch (error) {
43
+ spinner.info('CLI Update übersprungen (lokal installiert)');
44
+ }
45
+ }
46
+
47
+ // Plugin Update
48
+ if (!options.cliOnly) {
49
+ const spinner = ora('Aktualisiere Claude Code Plugins...').start();
50
+
51
+ try {
52
+ // Prüfe ob Marketplace installiert ist
53
+ const marketplacePath = await getMarketplacePath();
54
+
55
+ if (marketplacePath) {
56
+ await execa('git', ['-C', marketplacePath, 'pull', 'origin', 'main']);
57
+ spinner.succeed('Plugins aktualisiert!');
58
+ } else {
59
+ spinner.info('Kein Plugin Marketplace gefunden.');
60
+ logger.dim(' Führe "cl init" aus, um Plugins zu installieren.');
61
+ }
62
+ } catch (error) {
63
+ spinner.fail('Plugin Update fehlgeschlagen');
64
+ logger.dim(' Prüfe deine Internetverbindung.');
65
+ }
66
+ }
67
+
68
+ logger.blank();
69
+ }
70
+
71
+ async function getMarketplacePath(): Promise<string | null> {
72
+ // Versuche verschiedene Pfade
73
+ const possiblePaths = [
74
+ '.claude/plugins/clevermation-claude-plugins',
75
+ `${process.env.HOME}/.claude/plugins/clevermation-claude-plugins`,
76
+ ];
77
+
78
+ for (const p of possiblePaths) {
79
+ try {
80
+ const fs = await import('fs-extra');
81
+ if (await fs.default.pathExists(p)) {
82
+ return p;
83
+ }
84
+ } catch {
85
+ continue;
86
+ }
87
+ }
88
+
89
+ return null;
90
+ }
package/src/index.ts ADDED
@@ -0,0 +1,44 @@
1
+ import { Command } from 'commander';
2
+ import { createInitCommand } from './commands/init.js';
3
+ import { createSyncCommand } from './commands/sync.js';
4
+ import { createConfigCommand } from './commands/config.js';
5
+ import { createExplainCommand } from './commands/explain.js';
6
+ import { createUpdateCommand } from './commands/update.js';
7
+ import { createAuthCommand } from './commands/auth.js';
8
+ import { createDoctorCommand } from './commands/doctor.js';
9
+ import { createOpenCommand } from './commands/open.js';
10
+ import { handleError } from './utils/logger.js';
11
+ import { checkForUpdates } from './utils/auto-update.js';
12
+ import { silentPrerequisiteCheck } from './utils/prerequisites.js';
13
+
14
+ const VERSION = '0.3.2';
15
+
16
+ const program = new Command();
17
+
18
+ program
19
+ .name('cl')
20
+ .description('Clevermation CLI - Internes Tool für Claude Code Projekte')
21
+ .version(VERSION);
22
+
23
+ // Commands registrieren
24
+ program.addCommand(createInitCommand());
25
+ program.addCommand(createSyncCommand());
26
+ program.addCommand(createConfigCommand());
27
+ program.addCommand(createAuthCommand());
28
+ program.addCommand(createDoctorCommand());
29
+ program.addCommand(createOpenCommand());
30
+ program.addCommand(createExplainCommand());
31
+ program.addCommand(createUpdateCommand());
32
+
33
+ // Hintergrund-Tasks (non-blocking)
34
+ Promise.all([
35
+ // Auto-Update Check
36
+ checkForUpdates(VERSION),
37
+ // Prerequisite Check & Auto-Install
38
+ silentPrerequisiteCheck(),
39
+ ]).catch(() => {
40
+ // Fehler ignorieren - soll CLI nicht blockieren
41
+ });
42
+
43
+ // Ausführen
44
+ program.parseAsync(process.argv).catch(handleError);
@@ -0,0 +1,90 @@
1
+ import { z } from 'zod';
2
+
3
+ // Globale CLI Konfiguration (~/.clevermation/config.json)
4
+ export const GlobalConfigSchema = z.object({
5
+ version: z.string(),
6
+ defaultOrg: z.string().default('Clevermation'),
7
+ auth: z.object({
8
+ github: z
9
+ .object({
10
+ authenticated: z.boolean(),
11
+ username: z.string().optional(),
12
+ })
13
+ .optional(),
14
+ supabase: z
15
+ .object({
16
+ authenticated: z.boolean(),
17
+ accessToken: z.string().optional(),
18
+ })
19
+ .optional(),
20
+ n8n: z
21
+ .object({
22
+ url: z.string().optional(),
23
+ apiKey: z.string().optional(),
24
+ })
25
+ .optional(),
26
+ elevenlabs: z
27
+ .object({
28
+ apiKey: z.string().optional(),
29
+ })
30
+ .optional(),
31
+ }),
32
+ preferences: z.object({
33
+ defaultModel: z.enum(['opus', 'sonnet', 'haiku']).default('sonnet'),
34
+ defaultIDE: z.enum(['code', 'cursor', 'webstorm', 'zed']).default('code'),
35
+ autoUpdate: z.boolean().default(true),
36
+ optimaleEinstellungen: z.boolean().default(true),
37
+ }),
38
+ });
39
+
40
+ export type GlobalConfig = z.infer<typeof GlobalConfigSchema>;
41
+
42
+ // Projekt Konfiguration (.clevermation/config.json)
43
+ export const ProjectConfigSchema = z.object({
44
+ version: z.string(),
45
+ project: z.object({
46
+ name: z.string(),
47
+ type: z.enum(['customer', 'internal']),
48
+ customer: z.string().optional(),
49
+ description: z.string().optional(),
50
+ }),
51
+ services: z.object({
52
+ github: z.object({
53
+ enabled: z.literal(true),
54
+ repo: z.string(),
55
+ org: z.string().default('Clevermation'),
56
+ }),
57
+ supabase: z
58
+ .object({
59
+ enabled: z.boolean(),
60
+ projectRef: z.string().optional(),
61
+ url: z.string().optional(),
62
+ })
63
+ .optional(),
64
+ n8n: z
65
+ .object({
66
+ enabled: z.boolean(),
67
+ instanceUrl: z.string().optional(),
68
+ })
69
+ .optional(),
70
+ elevenlabs: z
71
+ .object({
72
+ enabled: z.boolean(),
73
+ })
74
+ .optional(),
75
+ }),
76
+ claudeCode: z.object({
77
+ plugins: z.array(z.string()),
78
+ marketplace: z.string().default('Clevermation/clevermation-claude-plugins'),
79
+ model: z.enum(['opus', 'sonnet', 'haiku']).default('sonnet'),
80
+ }),
81
+ createdAt: z.string(),
82
+ updatedAt: z.string(),
83
+ });
84
+
85
+ export type ProjectConfig = z.infer<typeof ProjectConfigSchema>;
86
+
87
+ // Service-Typen für Init
88
+ export type ServiceType = 'supabase' | 'n8n' | 'elevenlabs';
89
+ export type ProjectType = 'customer' | 'internal';
90
+ export type ModelType = 'opus' | 'sonnet' | 'haiku';
@@ -0,0 +1,169 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import chalk from 'chalk';
5
+ import { execa } from 'execa';
6
+ import semver from 'semver';
7
+
8
+ const UPDATE_CHECK_FILE = path.join(os.homedir(), '.clevermation', 'last-update-check.json');
9
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 Stunden
10
+ const PACKAGE_NAME = 'clevermation-cli';
11
+
12
+ interface UpdateCheckData {
13
+ lastCheck: number;
14
+ latestVersion: string | null;
15
+ notifiedVersion: string | null;
16
+ }
17
+
18
+ /**
19
+ * Prüft auf Updates und zeigt eine Benachrichtigung an.
20
+ * Läuft non-blocking im Hintergrund.
21
+ */
22
+ export async function checkForUpdates(currentVersion: string): Promise<void> {
23
+ try {
24
+ // Prüfe ob wir kürzlich gecheckt haben
25
+ const checkData = await loadCheckData();
26
+ const now = Date.now();
27
+
28
+ if (checkData.lastCheck && now - checkData.lastCheck < CHECK_INTERVAL_MS) {
29
+ // Kürzlich gecheckt - zeige cached Ergebnis wenn Update verfügbar
30
+ if (checkData.latestVersion &&
31
+ semver.gt(checkData.latestVersion, currentVersion) &&
32
+ checkData.notifiedVersion !== checkData.latestVersion) {
33
+ showUpdateNotification(currentVersion, checkData.latestVersion);
34
+ await saveCheckData({ ...checkData, notifiedVersion: checkData.latestVersion });
35
+ }
36
+ return;
37
+ }
38
+
39
+ // Neuer Check (non-blocking)
40
+ checkForUpdatesAsync(currentVersion, checkData);
41
+ } catch {
42
+ // Fehler ignorieren - Update-Check soll CLI nicht blockieren
43
+ }
44
+ }
45
+
46
+ async function checkForUpdatesAsync(
47
+ currentVersion: string,
48
+ checkData: UpdateCheckData
49
+ ): Promise<void> {
50
+ try {
51
+ // Versuche Version von npm/GitHub Packages zu holen
52
+ const latestVersion = await fetchLatestVersion();
53
+
54
+ if (latestVersion) {
55
+ const newCheckData: UpdateCheckData = {
56
+ lastCheck: Date.now(),
57
+ latestVersion,
58
+ notifiedVersion: checkData.notifiedVersion,
59
+ };
60
+
61
+ await saveCheckData(newCheckData);
62
+
63
+ if (semver.gt(latestVersion, currentVersion) &&
64
+ checkData.notifiedVersion !== latestVersion) {
65
+ showUpdateNotification(currentVersion, latestVersion);
66
+ await saveCheckData({ ...newCheckData, notifiedVersion: latestVersion });
67
+ }
68
+ }
69
+ } catch {
70
+ // Fehler ignorieren
71
+ }
72
+ }
73
+
74
+ async function fetchLatestVersion(): Promise<string | null> {
75
+ try {
76
+ // Versuche über npm view
77
+ const result = await execa('npm', ['view', PACKAGE_NAME, 'version'], {
78
+ timeout: 5000,
79
+ reject: false,
80
+ });
81
+
82
+ if (result.exitCode === 0 && result.stdout) {
83
+ return result.stdout.trim();
84
+ }
85
+
86
+ // Fallback: GitHub API für Releases
87
+ const response = await fetch(
88
+ 'https://api.github.com/repos/Clevermation/clevermation-cli/releases/latest',
89
+ { signal: AbortSignal.timeout(5000) }
90
+ );
91
+
92
+ if (response.ok) {
93
+ const data = await response.json() as { tag_name: string };
94
+ return data.tag_name.replace(/^v/, '');
95
+ }
96
+ } catch {
97
+ // Fehler ignorieren
98
+ }
99
+
100
+ return null;
101
+ }
102
+
103
+ async function loadCheckData(): Promise<UpdateCheckData> {
104
+ try {
105
+ if (await fs.pathExists(UPDATE_CHECK_FILE)) {
106
+ return await fs.readJson(UPDATE_CHECK_FILE);
107
+ }
108
+ } catch {
109
+ // Fehler ignorieren
110
+ }
111
+
112
+ return {
113
+ lastCheck: 0,
114
+ latestVersion: null,
115
+ notifiedVersion: null,
116
+ };
117
+ }
118
+
119
+ async function saveCheckData(data: UpdateCheckData): Promise<void> {
120
+ try {
121
+ await fs.ensureDir(path.dirname(UPDATE_CHECK_FILE));
122
+ await fs.writeJson(UPDATE_CHECK_FILE, data);
123
+ } catch {
124
+ // Fehler ignorieren
125
+ }
126
+ }
127
+
128
+ function showUpdateNotification(currentVersion: string, latestVersion: string): void {
129
+ const boxWidth = 50;
130
+ const line = '─'.repeat(boxWidth);
131
+
132
+ console.log();
133
+ console.log(chalk.yellow(` ╭${line}╮`));
134
+ console.log(chalk.yellow(` │${' '.repeat(boxWidth)}│`));
135
+ console.log(chalk.yellow(` │${centerText(`Update verfügbar: ${currentVersion} → ${chalk.green(latestVersion)}`, boxWidth)}│`));
136
+ console.log(chalk.yellow(` │${' '.repeat(boxWidth)}│`));
137
+ console.log(chalk.yellow(` │${centerText('Aktualisiere mit:', boxWidth)}│`));
138
+ console.log(chalk.yellow(` │${centerText(chalk.cyan('cl update'), boxWidth)}│`));
139
+ console.log(chalk.yellow(` │${' '.repeat(boxWidth)}│`));
140
+ console.log(chalk.yellow(` ╰${line}╯`));
141
+ console.log();
142
+ }
143
+
144
+ function centerText(text: string, width: number): string {
145
+ // Entferne ANSI Codes für Längenberechnung
146
+ const plainText = text.replace(/\x1b\[[0-9;]*m/g, '');
147
+ const padding = Math.max(0, Math.floor((width - plainText.length) / 2));
148
+ const rightPadding = width - plainText.length - padding;
149
+ return ' '.repeat(padding) + text + ' '.repeat(Math.max(0, rightPadding));
150
+ }
151
+
152
+ /**
153
+ * Führt ein automatisches Update durch wenn gewünscht.
154
+ */
155
+ export async function performAutoUpdate(): Promise<boolean> {
156
+ try {
157
+ console.log(chalk.blue(' Aktualisiere CLI...'));
158
+
159
+ await execa('bun', ['update', PACKAGE_NAME], { stdio: 'inherit' });
160
+
161
+ console.log(chalk.green(' ✓ CLI erfolgreich aktualisiert!'));
162
+ console.log(chalk.dim(' Starte die CLI neu um die neue Version zu nutzen.'));
163
+
164
+ return true;
165
+ } catch {
166
+ console.log(chalk.red(' ✗ Update fehlgeschlagen'));
167
+ return false;
168
+ }
169
+ }
@@ -0,0 +1,85 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import {
5
+ GlobalConfigSchema,
6
+ ProjectConfigSchema,
7
+ type GlobalConfig,
8
+ type ProjectConfig,
9
+ } from '../types/config.js';
10
+
11
+ // Pfade
12
+ const GLOBAL_CONFIG_DIR = path.join(os.homedir(), '.clevermation');
13
+ const GLOBAL_CONFIG_PATH = path.join(GLOBAL_CONFIG_DIR, 'config.json');
14
+ const PROJECT_CONFIG_DIR = '.clevermation';
15
+ const PROJECT_CONFIG_PATH = path.join(PROJECT_CONFIG_DIR, 'config.json');
16
+
17
+ // Default Configs
18
+ const DEFAULT_GLOBAL_CONFIG: GlobalConfig = {
19
+ version: '1.0.0',
20
+ defaultOrg: 'Clevermation',
21
+ auth: {},
22
+ preferences: {
23
+ defaultModel: 'sonnet',
24
+ defaultIDE: 'code',
25
+ autoUpdate: true,
26
+ optimaleEinstellungen: true,
27
+ },
28
+ };
29
+
30
+ // Globale Config
31
+ export async function loadGlobalConfig(): Promise<GlobalConfig> {
32
+ try {
33
+ if (await fs.pathExists(GLOBAL_CONFIG_PATH)) {
34
+ const data = await fs.readJson(GLOBAL_CONFIG_PATH);
35
+ return GlobalConfigSchema.parse(data);
36
+ }
37
+ } catch {
38
+ // Falls parsing fehlschlägt, default zurückgeben
39
+ }
40
+ return DEFAULT_GLOBAL_CONFIG;
41
+ }
42
+
43
+ export async function saveGlobalConfig(config: GlobalConfig): Promise<void> {
44
+ await fs.ensureDir(GLOBAL_CONFIG_DIR);
45
+ await fs.writeJson(GLOBAL_CONFIG_PATH, config, { spaces: 2 });
46
+ }
47
+
48
+ export async function updateGlobalConfig(
49
+ updates: Partial<GlobalConfig>
50
+ ): Promise<GlobalConfig> {
51
+ const current = await loadGlobalConfig();
52
+ const updated = { ...current, ...updates };
53
+ await saveGlobalConfig(updated);
54
+ return updated;
55
+ }
56
+
57
+ // Projekt Config
58
+ export async function loadProjectConfig(): Promise<ProjectConfig | null> {
59
+ try {
60
+ if (await fs.pathExists(PROJECT_CONFIG_PATH)) {
61
+ const data = await fs.readJson(PROJECT_CONFIG_PATH);
62
+ return ProjectConfigSchema.parse(data);
63
+ }
64
+ } catch {
65
+ // Falls parsing fehlschlägt
66
+ }
67
+ return null;
68
+ }
69
+
70
+ export async function saveProjectConfig(config: ProjectConfig): Promise<void> {
71
+ await fs.ensureDir(PROJECT_CONFIG_DIR);
72
+ await fs.writeJson(PROJECT_CONFIG_PATH, config, { spaces: 2 });
73
+ }
74
+
75
+ export async function hasProjectConfig(): Promise<boolean> {
76
+ return fs.pathExists(PROJECT_CONFIG_PATH);
77
+ }
78
+
79
+ export function getProjectConfigDir(): string {
80
+ return PROJECT_CONFIG_DIR;
81
+ }
82
+
83
+ export function getGlobalConfigDir(): string {
84
+ return GLOBAL_CONFIG_DIR;
85
+ }
@@ -0,0 +1,49 @@
1
+ import chalk from 'chalk';
2
+
3
+ export const logger = {
4
+ info: (msg: string) => console.log(chalk.blue(' info'), msg),
5
+ success: (msg: string) => console.log(chalk.green(' ✓'), msg),
6
+ warning: (msg: string) => console.log(chalk.yellow(' ⚠'), msg),
7
+ error: (msg: string) => console.log(chalk.red(' ✗'), msg),
8
+
9
+ step: (num: number, total: number, msg: string) => {
10
+ console.log(chalk.dim(` [${num}/${total}]`), msg);
11
+ },
12
+
13
+ title: (msg: string) => {
14
+ console.log(chalk.bold.blue(`\n ${msg}\n`));
15
+ },
16
+
17
+ dim: (msg: string) => console.log(chalk.dim(` ${msg}`)),
18
+
19
+ blank: () => console.log(''),
20
+ };
21
+
22
+ export class ClevermationError extends Error {
23
+ constructor(
24
+ message: string,
25
+ public hint?: string,
26
+ public code?: string
27
+ ) {
28
+ super(message);
29
+ this.name = 'ClevermationError';
30
+ }
31
+ }
32
+
33
+ export function handleError(error: unknown): never {
34
+ if (error instanceof ClevermationError) {
35
+ logger.error(error.message);
36
+ if (error.hint) {
37
+ logger.info(error.hint);
38
+ }
39
+ } else if (error instanceof Error) {
40
+ logger.error(error.message);
41
+ if (process.env.DEBUG) {
42
+ console.error(error.stack);
43
+ }
44
+ } else {
45
+ logger.error('Ein unerwarteter Fehler ist aufgetreten');
46
+ }
47
+
48
+ process.exit(1);
49
+ }