kaven-cli 0.1.0-alpha.1 → 0.3.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.
Files changed (36) hide show
  1. package/README.md +221 -45
  2. package/dist/commands/auth/login.js +97 -19
  3. package/dist/commands/auth/logout.js +4 -6
  4. package/dist/commands/auth/whoami.js +12 -11
  5. package/dist/commands/cache/index.js +43 -0
  6. package/dist/commands/config/index.js +128 -0
  7. package/dist/commands/init/index.js +209 -0
  8. package/dist/commands/init-ci/index.js +153 -0
  9. package/dist/commands/license/index.js +10 -0
  10. package/dist/commands/license/status.js +44 -0
  11. package/dist/commands/license/tier-table.js +46 -0
  12. package/dist/commands/marketplace/browse.js +219 -0
  13. package/dist/commands/marketplace/install.js +233 -29
  14. package/dist/commands/marketplace/list.js +94 -16
  15. package/dist/commands/module/doctor.js +143 -38
  16. package/dist/commands/module/publish.js +291 -0
  17. package/dist/commands/upgrade/check.js +162 -0
  18. package/dist/commands/upgrade/index.js +218 -0
  19. package/dist/core/AuthService.js +207 -14
  20. package/dist/core/CacheManager.js +151 -0
  21. package/dist/core/ConfigManager.js +165 -0
  22. package/dist/core/EnvManager.js +196 -0
  23. package/dist/core/ErrorRecovery.js +191 -0
  24. package/dist/core/LicenseService.js +118 -0
  25. package/dist/core/ModuleDoctor.js +286 -0
  26. package/dist/core/ModuleInstaller.js +136 -2
  27. package/dist/core/ProjectInitializer.js +117 -0
  28. package/dist/core/RegistryResolver.js +94 -0
  29. package/dist/core/ScriptRunner.js +72 -0
  30. package/dist/core/SignatureVerifier.js +72 -0
  31. package/dist/index.js +265 -20
  32. package/dist/infrastructure/MarketplaceClient.js +388 -64
  33. package/dist/infrastructure/errors.js +61 -0
  34. package/dist/types/auth.js +2 -0
  35. package/dist/types/marketplace.js +2 -0
  36. package/package.json +15 -2
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.configManager = exports.ConfigManager = exports.configSchema = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const os_1 = __importDefault(require("os"));
10
+ const zod_1 = require("zod");
11
+ const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), ".kaven");
12
+ const CONFIG_PATH = path_1.default.join(CONFIG_DIR, "config.json");
13
+ exports.configSchema = zod_1.z.object({
14
+ registry: zod_1.z.string().url().default("https://marketplace.kaven.sh"),
15
+ telemetry: zod_1.z.boolean().default(true),
16
+ theme: zod_1.z.enum(["light", "dark"]).default("dark"),
17
+ locale: zod_1.z.string().default("en-US"),
18
+ // Custom registry support
19
+ customRegistry: zod_1.z.string().url().optional(),
20
+ // For storing user preferences
21
+ lastLogin: zod_1.z.string().datetime().optional(),
22
+ projectDefaults: zod_1.z
23
+ .object({
24
+ dbUrl: zod_1.z.string().optional(),
25
+ emailProvider: zod_1.z.enum(["postmark", "resend", "ses", "smtp"]).optional(),
26
+ locale: zod_1.z.string().optional(),
27
+ currency: zod_1.z.string().optional(),
28
+ })
29
+ .optional(),
30
+ });
31
+ class ConfigManager {
32
+ constructor() {
33
+ this.config = {
34
+ registry: "https://marketplace.kaven.sh",
35
+ telemetry: true,
36
+ theme: "dark",
37
+ locale: "en-US",
38
+ };
39
+ }
40
+ async initialize() {
41
+ await fs_extra_1.default.ensureDir(CONFIG_DIR);
42
+ if (await fs_extra_1.default.pathExists(CONFIG_PATH)) {
43
+ try {
44
+ const raw = await fs_extra_1.default.readJson(CONFIG_PATH);
45
+ const parsed = exports.configSchema.safeParse(raw);
46
+ if (parsed.success) {
47
+ this.config = parsed.data;
48
+ }
49
+ else {
50
+ // If validation fails, use defaults
51
+ this.config = {
52
+ registry: "https://marketplace.kaven.sh",
53
+ telemetry: true,
54
+ theme: "dark",
55
+ locale: "en-US",
56
+ };
57
+ }
58
+ }
59
+ catch {
60
+ // If file is corrupted, start fresh
61
+ this.config = {
62
+ registry: "https://marketplace.kaven.sh",
63
+ telemetry: true,
64
+ theme: "dark",
65
+ locale: "en-US",
66
+ };
67
+ }
68
+ }
69
+ else {
70
+ // Initialize with defaults
71
+ this.config = {
72
+ registry: "https://marketplace.kaven.sh",
73
+ telemetry: true,
74
+ theme: "dark",
75
+ locale: "en-US",
76
+ };
77
+ await this.persist();
78
+ }
79
+ }
80
+ /**
81
+ * Get config value with env var override support
82
+ * Priority: ENV VAR > config file > CLI arg > default
83
+ */
84
+ get(key, envVarName) {
85
+ // Check environment variable override
86
+ if (envVarName) {
87
+ const envValue = process.env[envVarName];
88
+ if (envValue !== undefined) {
89
+ return envValue;
90
+ }
91
+ }
92
+ // Check config file
93
+ const value = this.config[key];
94
+ if (value !== undefined) {
95
+ return value;
96
+ }
97
+ // Return default from schema
98
+ const defaults = exports.configSchema.parse({});
99
+ return defaults[key];
100
+ }
101
+ /**
102
+ * Set config value and persist to disk
103
+ */
104
+ async set(key, value) {
105
+ const updateObj = { [key]: value };
106
+ const updated = exports.configSchema.safeParse({ ...this.config, ...updateObj });
107
+ if (!updated.success) {
108
+ const errors = updated.error.issues
109
+ .map((issue) => `${issue.path.join(".")}: ${issue.message}`)
110
+ .join(", ");
111
+ throw new Error(`Invalid config: ${errors}`);
112
+ }
113
+ this.config = updated.data;
114
+ await this.persist();
115
+ }
116
+ /**
117
+ * Get all config
118
+ */
119
+ getAll() {
120
+ return { ...this.config };
121
+ }
122
+ /**
123
+ * Reset config to defaults
124
+ */
125
+ async reset() {
126
+ this.config = {
127
+ registry: "https://marketplace.kaven.sh",
128
+ telemetry: true,
129
+ theme: "dark",
130
+ locale: "en-US",
131
+ };
132
+ await this.persist();
133
+ }
134
+ /**
135
+ * Persist config to disk
136
+ */
137
+ async persist() {
138
+ await fs_extra_1.default.ensureDir(CONFIG_DIR);
139
+ await fs_extra_1.default.writeJson(CONFIG_PATH, this.config, { spaces: 2 });
140
+ }
141
+ /**
142
+ * Get registry URL (custom or default)
143
+ */
144
+ getRegistry() {
145
+ return (this.config.customRegistry || this.config.registry || "https://marketplace.kaven.sh");
146
+ }
147
+ /**
148
+ * Check if telemetry is enabled (can be overridden by env var)
149
+ */
150
+ isTelemetryEnabled() {
151
+ if (process.env.KAVEN_TELEMETRY === "0") {
152
+ return false;
153
+ }
154
+ return this.config.telemetry !== false;
155
+ }
156
+ /**
157
+ * Get config directory path
158
+ */
159
+ getConfigDir() {
160
+ return CONFIG_DIR;
161
+ }
162
+ }
163
+ exports.ConfigManager = ConfigManager;
164
+ // Export singleton instance
165
+ exports.configManager = new ConfigManager();
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.EnvManager = void 0;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const readline = __importStar(require("readline"));
43
+ const chalk_1 = __importDefault(require("chalk"));
44
+ class EnvManager {
45
+ async injectEnvVars(moduleSlug, envVars, options) {
46
+ if (options.skipEnv || !envVars || envVars.length === 0) {
47
+ return { added: 0, skipped: 0 };
48
+ }
49
+ const envFilePath = path.join(options.projectDir, options.envFile ?? '.env');
50
+ const existingContent = this.readEnvFile(envFilePath);
51
+ const existingVars = this.parseEnvFile(existingContent);
52
+ const newVars = [];
53
+ let skipped = 0;
54
+ console.log(chalk_1.default.bold(`\n Environment variables for '${moduleSlug}':\n`));
55
+ for (const envDef of envVars) {
56
+ if (existingVars.has(envDef.name)) {
57
+ console.log(chalk_1.default.dim(` ${envDef.name} — already set, skipping`));
58
+ skipped++;
59
+ continue;
60
+ }
61
+ let value;
62
+ if (envDef.sensitive) {
63
+ value = await this.promptPassword(` ${envDef.name} (${envDef.description})${envDef.default ? ' [****]' : ''}: `);
64
+ if (!value && envDef.default)
65
+ value = envDef.default;
66
+ }
67
+ else {
68
+ const defaultHint = envDef.default ? ` [${envDef.default}]` : '';
69
+ value = await this.promptInput(` ${envDef.name} (${envDef.description})${defaultHint}: `, envDef.default);
70
+ }
71
+ if (envDef.required && !value) {
72
+ console.log(chalk_1.default.yellow(` ${envDef.name} is required.`));
73
+ value = envDef.sensitive
74
+ ? await this.promptPassword(` ${envDef.name}: `)
75
+ : await this.promptInput(` ${envDef.name}: `);
76
+ if (!value) {
77
+ console.log(chalk_1.default.yellow(` Skipping ${envDef.name} — set it manually in .env`));
78
+ skipped++;
79
+ continue;
80
+ }
81
+ }
82
+ newVars.push({ name: envDef.name, value });
83
+ }
84
+ if (newVars.length === 0) {
85
+ console.log(chalk_1.default.dim(' No new environment variables to add.'));
86
+ return { added: 0, skipped };
87
+ }
88
+ const markerBlock = this.buildMarkerBlock(moduleSlug, newVars);
89
+ this.appendToEnvFile(envFilePath, existingContent, markerBlock);
90
+ console.log(chalk_1.default.green(`\n Added ${newVars.length} environment variable(s) to ${options.envFile ?? '.env'}`));
91
+ return { added: newVars.length, skipped };
92
+ }
93
+ removeEnvVars(moduleSlug, options) {
94
+ const envFiles = ['.env', '.env.local', '.env.development', '.env.production'];
95
+ if (options.envFile)
96
+ envFiles.unshift(options.envFile);
97
+ let totalRemoved = 0;
98
+ for (const envFile of envFiles) {
99
+ const envFilePath = path.join(options.projectDir, envFile);
100
+ if (!fs.existsSync(envFilePath))
101
+ continue;
102
+ const content = fs.readFileSync(envFilePath, 'utf-8');
103
+ const beginMarker = `# [KAVEN_MODULE:${moduleSlug} BEGIN]`;
104
+ const endMarker = `# [KAVEN_MODULE:${moduleSlug} END]`;
105
+ const beginIdx = content.indexOf(beginMarker);
106
+ const endIdx = content.indexOf(endMarker);
107
+ if (beginIdx === -1 || endIdx === -1)
108
+ continue;
109
+ const block = content.substring(beginIdx, endIdx + endMarker.length);
110
+ const varCount = block.split('\n').filter(l => /^[A-Z_]+=/.test(l)).length;
111
+ const before = content.substring(0, beginIdx).replace(/\n+$/, '\n');
112
+ const after = content.substring(endIdx + endMarker.length + 1);
113
+ fs.writeFileSync(envFilePath, before + after);
114
+ totalRemoved += varCount;
115
+ if (varCount > 0) {
116
+ console.log(chalk_1.default.dim(` Removed ${varCount} env var(s) from ${envFile}`));
117
+ }
118
+ }
119
+ return totalRemoved;
120
+ }
121
+ readEnvFile(filePath) {
122
+ try {
123
+ return fs.readFileSync(filePath, 'utf-8');
124
+ }
125
+ catch {
126
+ return '';
127
+ }
128
+ }
129
+ parseEnvFile(content) {
130
+ const vars = new Map();
131
+ for (const line of content.split('\n')) {
132
+ const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
133
+ if (match)
134
+ vars.set(match[1], match[2]);
135
+ }
136
+ return vars;
137
+ }
138
+ buildMarkerBlock(moduleSlug, vars) {
139
+ return [
140
+ `# [KAVEN_MODULE:${moduleSlug} BEGIN]`,
141
+ ...vars.map(v => `${v.name}=${v.value}`),
142
+ `# [KAVEN_MODULE:${moduleSlug} END]`,
143
+ ].join('\n');
144
+ }
145
+ appendToEnvFile(filePath, existingContent, block) {
146
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
147
+ const separator = existingContent.endsWith('\n') || existingContent === '' ? '\n' : '\n\n';
148
+ fs.writeFileSync(filePath, existingContent + separator + block + '\n');
149
+ }
150
+ promptInput(message, defaultValue) {
151
+ return new Promise(resolve => {
152
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
153
+ rl.question(message, answer => {
154
+ rl.close();
155
+ resolve(answer || defaultValue || '');
156
+ });
157
+ });
158
+ }
159
+ promptPassword(message) {
160
+ return new Promise(resolve => {
161
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
162
+ // Hide input by listening to keypress but readline doesn't natively support this
163
+ // For simplicity, use standard prompt (production would use a proper password lib)
164
+ process.stdout.write(message);
165
+ process.stdin.setRawMode?.(true);
166
+ process.stdin.resume();
167
+ let password = '';
168
+ const handler = (key) => {
169
+ const char = key.toString();
170
+ if (char === '\r' || char === '\n') {
171
+ process.stdin.setRawMode?.(false);
172
+ process.stdin.pause();
173
+ process.stdin.removeListener('data', handler);
174
+ process.stdout.write('\n');
175
+ rl.close();
176
+ resolve(password);
177
+ }
178
+ else if (char === '\u0003') {
179
+ process.exit();
180
+ }
181
+ else if (char === '\u007f') {
182
+ password = password.slice(0, -1);
183
+ process.stdout.clearLine(0);
184
+ process.stdout.cursorTo(0);
185
+ process.stdout.write(message + '*'.repeat(password.length));
186
+ }
187
+ else {
188
+ password += char;
189
+ process.stdout.write('*');
190
+ }
191
+ };
192
+ process.stdin.on('data', handler);
193
+ });
194
+ }
195
+ }
196
+ exports.EnvManager = EnvManager;
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ErrorRecovery = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const os_1 = __importDefault(require("os"));
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const ora_1 = __importDefault(require("ora"));
12
+ /**
13
+ * C2.7: Error recovery and validation system
14
+ */
15
+ class ErrorRecovery {
16
+ constructor() {
17
+ this.backupDir = path_1.default.join(os_1.default.homedir(), ".kaven", "backups");
18
+ }
19
+ /**
20
+ * Create a backup of the project state before operation
21
+ */
22
+ async createBackup(projectPath, operationName) {
23
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-").substring(0, 19);
24
+ const backupName = `${operationName}-${timestamp}`;
25
+ const backupPath = path_1.default.join(this.backupDir, backupName);
26
+ try {
27
+ await fs_extra_1.default.ensureDir(this.backupDir);
28
+ // Backup key files
29
+ const files = ["package.json", "pnpm-lock.yaml", ".env", "prisma/schema.prisma"];
30
+ for (const file of files) {
31
+ const source = path_1.default.join(projectPath, file);
32
+ const dest = path_1.default.join(backupPath, file);
33
+ if (await fs_extra_1.default.pathExists(source)) {
34
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(dest));
35
+ await fs_extra_1.default.copy(source, dest);
36
+ }
37
+ }
38
+ return backupPath;
39
+ }
40
+ catch (error) {
41
+ throw new Error(`Failed to create backup: ${error instanceof Error ? error.message : String(error)}`);
42
+ }
43
+ }
44
+ /**
45
+ * Rollback from backup
46
+ */
47
+ async rollback(projectPath, backupPath) {
48
+ try {
49
+ if (!(await fs_extra_1.default.pathExists(backupPath))) {
50
+ throw new Error(`Backup not found at ${backupPath}`);
51
+ }
52
+ const files = ["package.json", "pnpm-lock.yaml", ".env", "prisma/schema.prisma"];
53
+ for (const file of files) {
54
+ const source = path_1.default.join(backupPath, file);
55
+ const dest = path_1.default.join(projectPath, file);
56
+ if (await fs_extra_1.default.pathExists(source)) {
57
+ await fs_extra_1.default.copy(source, dest, { overwrite: true });
58
+ }
59
+ }
60
+ }
61
+ catch (error) {
62
+ throw new Error(`Failed to rollback: ${error instanceof Error ? error.message : String(error)}`);
63
+ }
64
+ }
65
+ /**
66
+ * Pre-operation validation
67
+ */
68
+ async validatePreConditions(projectPath) {
69
+ const errors = [];
70
+ // Check package.json exists
71
+ if (!(await fs_extra_1.default.pathExists(path_1.default.join(projectPath, "package.json")))) {
72
+ errors.push("package.json not found");
73
+ }
74
+ // Check node_modules exists
75
+ if (!(await fs_extra_1.default.pathExists(path_1.default.join(projectPath, "node_modules")))) {
76
+ errors.push("Dependencies not installed. Run: pnpm install");
77
+ }
78
+ // Check if git repo (for safety)
79
+ const gitDir = path_1.default.join(projectPath, ".git");
80
+ if (!(await fs_extra_1.default.pathExists(gitDir))) {
81
+ errors.push("Not a git repository. Initialize with: git init && git add . && git commit");
82
+ }
83
+ return {
84
+ valid: errors.length === 0,
85
+ errors,
86
+ };
87
+ }
88
+ /**
89
+ * Post-operation health check
90
+ */
91
+ async validatePostConditions(projectPath) {
92
+ const issues = [];
93
+ // Check package.json is valid JSON
94
+ try {
95
+ const packageJson = await fs_extra_1.default.readJson(path_1.default.join(projectPath, "package.json"));
96
+ if (!packageJson.name) {
97
+ issues.push("package.json missing name field");
98
+ }
99
+ }
100
+ catch {
101
+ issues.push("package.json is invalid JSON");
102
+ }
103
+ // Check prisma schema if it exists
104
+ const schemaPath = path_1.default.join(projectPath, "prisma", "schema.prisma");
105
+ if (await fs_extra_1.default.pathExists(schemaPath)) {
106
+ const content = await fs_extra_1.default.readFile(schemaPath, "utf-8");
107
+ if (!content.includes("datasource db")) {
108
+ issues.push("prisma/schema.prisma missing datasource");
109
+ }
110
+ }
111
+ // Check .env exists or .env.example exists
112
+ const envPath = path_1.default.join(projectPath, ".env");
113
+ const envExamplePath = path_1.default.join(projectPath, ".env.example");
114
+ if (!(await fs_extra_1.default.pathExists(envPath)) && !(await fs_extra_1.default.pathExists(envExamplePath))) {
115
+ issues.push("No .env or .env.example file found");
116
+ }
117
+ return {
118
+ healthy: issues.length === 0,
119
+ issues,
120
+ };
121
+ }
122
+ /**
123
+ * Run operation with automatic rollback on failure
124
+ */
125
+ async withRollback(projectPath, operationName, operation) {
126
+ // Pre-validation
127
+ const preCheck = await this.validatePreConditions(projectPath);
128
+ if (!preCheck.valid) {
129
+ throw new Error(`Pre-conditions not met:\n${preCheck.errors.map((e) => ` - ${e}`).join("\n")}`);
130
+ }
131
+ // Create backup
132
+ const spinner = (0, ora_1.default)("Creating backup...").start();
133
+ const backupPath = await this.createBackup(projectPath, operationName);
134
+ spinner.succeed(`Backup created at ${backupPath}`);
135
+ try {
136
+ // Run operation
137
+ const result = await operation();
138
+ // Post-validation
139
+ const postCheck = await this.validatePostConditions(projectPath);
140
+ if (!postCheck.healthy) {
141
+ spinner.warn("Post-operation issues detected:");
142
+ for (const issue of postCheck.issues) {
143
+ console.log(chalk_1.default.yellow(` ⚠ ${issue}`));
144
+ }
145
+ }
146
+ return result;
147
+ }
148
+ catch (error) {
149
+ // Rollback on failure
150
+ spinner.start("Rolling back...");
151
+ try {
152
+ await this.rollback(projectPath, backupPath);
153
+ spinner.succeed("Rolled back successfully");
154
+ }
155
+ catch (rollbackError) {
156
+ spinner.fail(`Rollback failed: ${rollbackError}`);
157
+ console.error(chalk_1.default.red("Manual intervention required:"));
158
+ console.error(chalk_1.default.gray(`Backup available at: ${backupPath}`));
159
+ }
160
+ throw error;
161
+ }
162
+ }
163
+ /**
164
+ * List available backups
165
+ */
166
+ async listBackups() {
167
+ if (!(await fs_extra_1.default.pathExists(this.backupDir))) {
168
+ return [];
169
+ }
170
+ const entries = await fs_extra_1.default.readdir(this.backupDir);
171
+ return entries.map((name) => ({
172
+ name,
173
+ path: path_1.default.join(this.backupDir, name),
174
+ timestamp: name.substring(name.lastIndexOf("-") + 1),
175
+ }));
176
+ }
177
+ /**
178
+ * Clean old backups (keep last N)
179
+ */
180
+ async cleanupBackups(keepCount = 5) {
181
+ const backups = await this.listBackups();
182
+ if (backups.length <= keepCount) {
183
+ return;
184
+ }
185
+ const toDelete = backups.slice(keepCount);
186
+ for (const backup of toDelete) {
187
+ await fs_extra_1.default.remove(backup.path);
188
+ }
189
+ }
190
+ }
191
+ exports.ErrorRecovery = ErrorRecovery;
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.LicenseService = void 0;
40
+ const promises_1 = __importDefault(require("fs/promises"));
41
+ const path_1 = __importDefault(require("path"));
42
+ const os_1 = __importDefault(require("os"));
43
+ const CACHE_FILE = path_1.default.join(os_1.default.homedir(), '.kaven', 'cache', 'licenses.json');
44
+ const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
45
+ class LicenseService {
46
+ constructor() {
47
+ this.cacheDir = path_1.default.join(os_1.default.homedir(), '.kaven', 'cache');
48
+ }
49
+ /** Luhn mod-31 checksum format check (KAVEN-{TIER}-{8RANDOM}-{CHECKSUM}) */
50
+ isValidFormat(licenseKey) {
51
+ const pattern = /^KAVEN-(STARTER|COMPLETE|PRO|ENTERPRISE)-[A-Z0-9]{8}-[A-Z0-9]{2}$/;
52
+ return pattern.test(licenseKey);
53
+ }
54
+ async readCache() {
55
+ try {
56
+ const raw = await promises_1.default.readFile(CACHE_FILE, 'utf-8');
57
+ return JSON.parse(raw);
58
+ }
59
+ catch {
60
+ return {};
61
+ }
62
+ }
63
+ async writeCache(cache) {
64
+ await promises_1.default.mkdir(this.cacheDir, { recursive: true });
65
+ await promises_1.default.writeFile(CACHE_FILE, JSON.stringify(cache, null, 2), 'utf-8');
66
+ }
67
+ isCacheValid(entry) {
68
+ return Date.now() - entry.validatedAt < CACHE_TTL_MS;
69
+ }
70
+ async getCached(licenseKey) {
71
+ const cache = await this.readCache();
72
+ const entry = cache[licenseKey];
73
+ if (entry && this.isCacheValid(entry))
74
+ return entry;
75
+ return null;
76
+ }
77
+ async setCached(entry) {
78
+ const cache = await this.readCache();
79
+ cache[entry.key] = { ...entry, validatedAt: Date.now() };
80
+ await this.writeCache(cache);
81
+ }
82
+ async validateLicense(licenseKey, requiredTier) {
83
+ // 1. Check cache first
84
+ const cached = await this.getCached(licenseKey);
85
+ if (cached) {
86
+ return { valid: true, tier: cached.tier, expiresAt: cached.expiresAt, source: 'cache' };
87
+ }
88
+ // 2. Format check (offline Luhn-style)
89
+ if (!this.isValidFormat(licenseKey)) {
90
+ throw new Error('Invalid license key format');
91
+ }
92
+ // 3. API validation
93
+ const { MarketplaceClient } = await Promise.resolve().then(() => __importStar(require('../infrastructure/MarketplaceClient.js')));
94
+ const client = new MarketplaceClient();
95
+ const result = await client.validateLicense(licenseKey, requiredTier);
96
+ // Cache on success
97
+ await this.setCached({
98
+ key: licenseKey,
99
+ tier: result.tier,
100
+ expiresAt: result.expiresAt,
101
+ validatedAt: Date.now(),
102
+ });
103
+ return { ...result, source: 'api' };
104
+ }
105
+ async getLicenseStatus(licenseKey) {
106
+ const { MarketplaceClient } = await Promise.resolve().then(() => __importStar(require('../infrastructure/MarketplaceClient.js')));
107
+ const client = new MarketplaceClient();
108
+ return client.getLicenseStatus(licenseKey);
109
+ }
110
+ tierLevel(tier) {
111
+ const levels = { STARTER: 1, COMPLETE: 2, PRO: 3, ENTERPRISE: 4 };
112
+ return levels[tier.toUpperCase()] ?? 0;
113
+ }
114
+ userHasRequiredTier(userTier, requiredTier) {
115
+ return this.tierLevel(userTier) >= this.tierLevel(requiredTier);
116
+ }
117
+ }
118
+ exports.LicenseService = LicenseService;