tuneprompt 1.0.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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +151 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +146 -0
  5. package/dist/commands/activate.d.ts +1 -0
  6. package/dist/commands/activate.js +91 -0
  7. package/dist/commands/fix.d.ts +1 -0
  8. package/dist/commands/fix.js +187 -0
  9. package/dist/commands/history.d.ts +5 -0
  10. package/dist/commands/history.js +63 -0
  11. package/dist/commands/init.d.ts +1 -0
  12. package/dist/commands/init.js +96 -0
  13. package/dist/commands/run.d.ts +9 -0
  14. package/dist/commands/run.js +216 -0
  15. package/dist/db/migrate.d.ts +2 -0
  16. package/dist/db/migrate.js +8 -0
  17. package/dist/engine/constraintExtractor.d.ts +8 -0
  18. package/dist/engine/constraintExtractor.js +54 -0
  19. package/dist/engine/loader.d.ts +5 -0
  20. package/dist/engine/loader.js +74 -0
  21. package/dist/engine/metaPrompt.d.ts +11 -0
  22. package/dist/engine/metaPrompt.js +129 -0
  23. package/dist/engine/optimizer.d.ts +26 -0
  24. package/dist/engine/optimizer.js +246 -0
  25. package/dist/engine/reporter.d.ts +7 -0
  26. package/dist/engine/reporter.js +58 -0
  27. package/dist/engine/runner.d.ts +9 -0
  28. package/dist/engine/runner.js +169 -0
  29. package/dist/engine/shadowTester.d.ts +11 -0
  30. package/dist/engine/shadowTester.js +156 -0
  31. package/dist/index.d.ts +7 -0
  32. package/dist/index.js +26 -0
  33. package/dist/providers/anthropic.d.ts +12 -0
  34. package/dist/providers/anthropic.js +51 -0
  35. package/dist/providers/base.d.ts +15 -0
  36. package/dist/providers/base.js +10 -0
  37. package/dist/providers/openai.d.ts +12 -0
  38. package/dist/providers/openai.js +58 -0
  39. package/dist/providers/openrouter.d.ts +11 -0
  40. package/dist/providers/openrouter.js +83 -0
  41. package/dist/scoring/exact-match.d.ts +1 -0
  42. package/dist/scoring/exact-match.js +8 -0
  43. package/dist/scoring/json-validator.d.ts +4 -0
  44. package/dist/scoring/json-validator.js +29 -0
  45. package/dist/scoring/semantic.d.ts +8 -0
  46. package/dist/scoring/semantic.js +107 -0
  47. package/dist/services/cloud.service.d.ts +49 -0
  48. package/dist/services/cloud.service.js +82 -0
  49. package/dist/storage/database.d.ts +10 -0
  50. package/dist/storage/database.js +179 -0
  51. package/dist/types/fix.d.ts +28 -0
  52. package/dist/types/fix.js +2 -0
  53. package/dist/types/index.d.ts +58 -0
  54. package/dist/types/index.js +2 -0
  55. package/dist/utils/analytics.d.ts +2 -0
  56. package/dist/utils/analytics.js +22 -0
  57. package/dist/utils/config.d.ts +3 -0
  58. package/dist/utils/config.js +70 -0
  59. package/dist/utils/errorHandler.d.ts +14 -0
  60. package/dist/utils/errorHandler.js +40 -0
  61. package/dist/utils/license.d.ts +40 -0
  62. package/dist/utils/license.js +207 -0
  63. package/dist/utils/storage.d.ts +2 -0
  64. package/dist/utils/storage.js +25 -0
  65. package/package.json +76 -0
@@ -0,0 +1,179 @@
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.TestDatabase = void 0;
40
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
41
+ const path = __importStar(require("path"));
42
+ const os = __importStar(require("os"));
43
+ const fs = __importStar(require("fs"));
44
+ const migrate_1 = require("../db/migrate");
45
+ class TestDatabase {
46
+ db;
47
+ constructor(dbPath) {
48
+ const defaultPath = path.join(os.homedir(), '.tuneprompt', 'history.db');
49
+ const dir = path.dirname(dbPath || defaultPath);
50
+ if (!fs.existsSync(dir)) {
51
+ fs.mkdirSync(dir, { recursive: true });
52
+ }
53
+ this.db = new better_sqlite3_1.default(dbPath || defaultPath);
54
+ this.migrate();
55
+ }
56
+ migrate() {
57
+ this.db.exec(`
58
+ CREATE TABLE IF NOT EXISTS test_runs (
59
+ id TEXT PRIMARY KEY,
60
+ timestamp INTEGER NOT NULL,
61
+ total_tests INTEGER NOT NULL,
62
+ passed INTEGER NOT NULL,
63
+ failed INTEGER NOT NULL,
64
+ duration INTEGER NOT NULL
65
+ );
66
+
67
+ CREATE TABLE IF NOT EXISTS test_results (
68
+ id TEXT PRIMARY KEY,
69
+ run_id TEXT NOT NULL,
70
+ description TEXT NOT NULL,
71
+ prompt TEXT NOT NULL,
72
+ variables TEXT,
73
+ expect TEXT,
74
+ config TEXT,
75
+ file_path TEXT,
76
+ status TEXT NOT NULL,
77
+ score REAL NOT NULL,
78
+ actual_output TEXT,
79
+ expected_output TEXT,
80
+ error TEXT,
81
+ duration INTEGER NOT NULL,
82
+ tokens INTEGER,
83
+ cost REAL,
84
+ FOREIGN KEY (run_id) REFERENCES test_runs(id)
85
+ );
86
+
87
+ CREATE INDEX IF NOT EXISTS idx_run_timestamp ON test_runs(timestamp);
88
+ CREATE INDEX IF NOT EXISTS idx_result_run ON test_results(run_id);
89
+ `);
90
+ // Run external migrations (Phase 2)
91
+ // Note: runMigrations is async but contains synchronous better-sqlite3 calls
92
+ // so it executes immediately. We catch any promise rejection just in case.
93
+ (0, migrate_1.runMigrations)(this.db).catch((err) => {
94
+ console.error('Phase 2 migration failed:', err);
95
+ });
96
+ }
97
+ saveRun(run) {
98
+ const insertRun = this.db.prepare(`
99
+ INSERT INTO test_runs (id, timestamp, total_tests, passed, failed, duration)
100
+ VALUES (?, ?, ?, ?, ?, ?)
101
+ `);
102
+ insertRun.run(run.id, run.timestamp.getTime(), run.totalTests, run.passed, run.failed, run.duration);
103
+ const insertResult = this.db.prepare(`
104
+ INSERT INTO test_results
105
+ (id, run_id, description, prompt, variables, expect, config, file_path, status, score, actual_output, expected_output, error, duration, tokens, cost)
106
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
107
+ `);
108
+ for (const result of run.results) {
109
+ insertResult.run(result.id, run.id, result.testCase.description, typeof result.testCase.prompt === 'string' ? result.testCase.prompt : JSON.stringify(result.testCase.prompt), result.testCase.variables ? JSON.stringify(result.testCase.variables) : null, typeof result.testCase.expect === 'string' ? result.testCase.expect : JSON.stringify(result.testCase.expect), result.testCase.config ? JSON.stringify(result.testCase.config) : null, result.testCase.filePath || null, result.status, result.score, result.actualOutput, result.expectedOutput, result.error || null, result.metadata.duration, result.metadata.tokens || null, result.metadata.cost || null);
110
+ }
111
+ }
112
+ getRecentRuns(limit = 10) {
113
+ const runs = this.db.prepare(`
114
+ SELECT * FROM test_runs
115
+ ORDER BY timestamp DESC
116
+ LIMIT ?
117
+ `).all(limit);
118
+ return runs.map(run => ({
119
+ id: run.id,
120
+ timestamp: new Date(run.timestamp),
121
+ totalTests: run.total_tests,
122
+ passed: run.passed,
123
+ failed: run.failed,
124
+ duration: run.duration,
125
+ results: this.getRunResults(run.id)
126
+ }));
127
+ }
128
+ getRunResults(runId) {
129
+ const results = this.db.prepare(`
130
+ SELECT * FROM test_results WHERE run_id = ?
131
+ `).all(runId);
132
+ return results.map(r => {
133
+ let prompt = r.prompt;
134
+ try {
135
+ if (prompt.startsWith('{'))
136
+ prompt = JSON.parse(prompt);
137
+ }
138
+ catch (e) { }
139
+ let variables = null;
140
+ try {
141
+ if (r.variables)
142
+ variables = JSON.parse(r.variables);
143
+ }
144
+ catch (e) { }
145
+ let config = null;
146
+ try {
147
+ if (r.config)
148
+ config = JSON.parse(r.config);
149
+ }
150
+ catch (e) { }
151
+ return {
152
+ id: r.id,
153
+ testCase: {
154
+ description: r.description,
155
+ prompt: prompt,
156
+ variables: variables,
157
+ expect: r.expect || r.expected_output,
158
+ config: config,
159
+ filePath: r.file_path
160
+ },
161
+ status: r.status,
162
+ score: r.score,
163
+ actualOutput: r.actual_output,
164
+ expectedOutput: r.expected_output,
165
+ error: r.error,
166
+ metadata: {
167
+ duration: r.duration,
168
+ timestamp: new Date(),
169
+ tokens: r.tokens,
170
+ cost: r.cost
171
+ }
172
+ };
173
+ });
174
+ }
175
+ close() {
176
+ this.db.close();
177
+ }
178
+ }
179
+ exports.TestDatabase = TestDatabase;
@@ -0,0 +1,28 @@
1
+ export interface FailedTest {
2
+ id: string;
3
+ description: string;
4
+ prompt: string;
5
+ input?: Record<string, any>;
6
+ expectedOutput: string;
7
+ actualOutput: string;
8
+ score: number;
9
+ threshold: number;
10
+ errorType: 'semantic' | 'json' | 'exact' | 'length';
11
+ errorMessage: string;
12
+ }
13
+ export interface OptimizationResult {
14
+ originalPrompt: string;
15
+ optimizedPrompt: string;
16
+ reasoning: string;
17
+ confidence: number;
18
+ testResults: {
19
+ score: number;
20
+ passed: boolean;
21
+ output: string;
22
+ };
23
+ }
24
+ export interface FixCandidate {
25
+ prompt: string;
26
+ score: number;
27
+ reasoning: string;
28
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,58 @@
1
+ export interface TestCase {
2
+ description: string;
3
+ prompt: string | {
4
+ system?: string;
5
+ user: string;
6
+ };
7
+ variables?: Record<string, any>;
8
+ expect: string | Record<string, any>;
9
+ config?: {
10
+ threshold?: number;
11
+ method?: 'exact' | 'semantic' | 'json' | 'llm-judge';
12
+ model?: string;
13
+ provider?: 'openai' | 'anthropic' | 'openrouter';
14
+ };
15
+ filePath?: string;
16
+ }
17
+ export interface TestResult {
18
+ id: string;
19
+ testCase: TestCase;
20
+ status: 'pass' | 'fail' | 'error';
21
+ score: number;
22
+ actualOutput: string;
23
+ expectedOutput: string;
24
+ error?: string;
25
+ metadata: {
26
+ duration: number;
27
+ timestamp: Date;
28
+ tokens?: number;
29
+ cost?: number;
30
+ provider?: string;
31
+ };
32
+ }
33
+ export interface TestRun {
34
+ id: string;
35
+ timestamp: Date;
36
+ totalTests: number;
37
+ passed: number;
38
+ failed: number;
39
+ duration: number;
40
+ results: TestResult[];
41
+ }
42
+ export interface ProviderConfig {
43
+ apiKey: string;
44
+ model: string;
45
+ baseURL?: string;
46
+ maxTokens?: number;
47
+ temperature?: number;
48
+ }
49
+ export interface TunePromptConfig {
50
+ providers: {
51
+ openai?: ProviderConfig;
52
+ anthropic?: ProviderConfig;
53
+ openrouter?: ProviderConfig;
54
+ };
55
+ threshold?: number;
56
+ testDir?: string;
57
+ outputFormat?: 'json' | 'table' | 'both';
58
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ export declare function trackEvent(userId: string, event: string, properties?: Record<string, any>): void;
2
+ export declare function trackFixAttempt(userId: string, testCount: number, success: boolean): Promise<void>;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.trackEvent = trackEvent;
4
+ exports.trackFixAttempt = trackFixAttempt;
5
+ const { PostHog } = require('posthog-node');
6
+ const client = new PostHog(process.env.POSTHOG_KEY || 'phc_xxxxx', { host: 'https://app.posthog.com' });
7
+ function trackEvent(userId, event, properties) {
8
+ if (process.env.NODE_ENV === 'production') {
9
+ client.capture({
10
+ distinctId: userId,
11
+ event,
12
+ properties
13
+ });
14
+ }
15
+ }
16
+ // Track in fix command
17
+ async function trackFixAttempt(userId, testCount, success) {
18
+ trackEvent(userId, 'fix_attempted', {
19
+ test_count: testCount,
20
+ success
21
+ });
22
+ }
@@ -0,0 +1,3 @@
1
+ import { TunePromptConfig } from '../types';
2
+ export declare function loadConfig(configPath?: string): Promise<TunePromptConfig>;
3
+ export declare function getDefaultConfigTemplate(): string;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadConfig = loadConfig;
4
+ exports.getDefaultConfigTemplate = getDefaultConfigTemplate;
5
+ const cosmiconfig_1 = require("cosmiconfig");
6
+ const explorer = (0, cosmiconfig_1.cosmiconfig)('tuneprompt');
7
+ async function loadConfig(configPath) {
8
+ let result;
9
+ if (configPath) {
10
+ result = await explorer.load(configPath);
11
+ }
12
+ else {
13
+ result = await explorer.search();
14
+ }
15
+ if (!result || !result.config) {
16
+ throw new Error('No tuneprompt config found. Run "tuneprompt init" to create one.');
17
+ }
18
+ return validateConfig(result.config);
19
+ }
20
+ function validateConfig(config) {
21
+ if (!config.providers || Object.keys(config.providers).length === 0) {
22
+ throw new Error('At least one provider must be configured');
23
+ }
24
+ // Validate API keys
25
+ for (const [provider, cfg] of Object.entries(config.providers)) {
26
+ if (!cfg.apiKey) {
27
+ throw new Error(`API key missing for provider: ${provider}`);
28
+ }
29
+ }
30
+ return {
31
+ threshold: config.threshold || 0.8,
32
+ testDir: config.testDir || './tests',
33
+ outputFormat: config.outputFormat || 'both',
34
+ ...config
35
+ };
36
+ }
37
+ function getDefaultConfigTemplate() {
38
+ return `module.exports = {
39
+ providers: {
40
+ openai: {
41
+ apiKey: process.env.OPENAI_API_KEY,
42
+ model: 'gpt-4o',
43
+ maxTokens: 1000,
44
+ temperature: 0.7
45
+ },
46
+ anthropic: {
47
+ apiKey: process.env.ANTHROPIC_API_KEY,
48
+ model: 'claude-sonnet-4-20250514',
49
+ maxTokens: 1000,
50
+ temperature: 0.7
51
+ },
52
+ openrouter: {
53
+ apiKey: process.env.OPENROUTER_API_KEY,
54
+ model: 'nvidia/nemotron-3-nano-30b-a3b:free',
55
+ maxTokens: 400,
56
+ temperature: 0.7
57
+ }
58
+ },
59
+
60
+ // Global semantic similarity threshold
61
+ threshold: 0.8,
62
+
63
+ // Directory containing test files
64
+ testDir: './tests',
65
+
66
+ // Output format: 'json', 'table', or 'both'
67
+ outputFormat: 'both'
68
+ };
69
+ `;
70
+ }
@@ -0,0 +1,14 @@
1
+ export declare class TunePromptError extends Error {
2
+ code: string;
3
+ details?: any | undefined;
4
+ constructor(message: string, code: string, details?: any | undefined);
5
+ }
6
+ export declare function handleError(error: any): void;
7
+ export declare const Errors: {
8
+ NO_LICENSE: TunePromptError;
9
+ INVALID_LICENSE: TunePromptError;
10
+ API_KEY_MISSING: TunePromptError;
11
+ NO_FAILED_TESTS: TunePromptError;
12
+ NETWORK_ERROR: TunePromptError;
13
+ OPTIMIZATION_FAILED: TunePromptError;
14
+ };
@@ -0,0 +1,40 @@
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.Errors = exports.TunePromptError = void 0;
7
+ exports.handleError = handleError;
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ class TunePromptError extends Error {
10
+ code;
11
+ details;
12
+ constructor(message, code, details) {
13
+ super(message);
14
+ this.code = code;
15
+ this.details = details;
16
+ this.name = 'TunePromptError';
17
+ }
18
+ }
19
+ exports.TunePromptError = TunePromptError;
20
+ function handleError(error) {
21
+ if (error instanceof TunePromptError) {
22
+ console.error(chalk_1.default.red(`\n❌ Error [${error.code}]: ${error.message}\n`));
23
+ if (error.details) {
24
+ console.error(chalk_1.default.gray(JSON.stringify(error.details, null, 2)));
25
+ }
26
+ }
27
+ else {
28
+ console.error(chalk_1.default.red('\n❌ Unexpected error:'), error.message);
29
+ }
30
+ process.exit(1);
31
+ }
32
+ // Common errors
33
+ exports.Errors = {
34
+ NO_LICENSE: new TunePromptError('No active license found', 'NO_LICENSE', { suggestion: 'Run: tuneprompt activate <subscription-id>' }),
35
+ INVALID_LICENSE: new TunePromptError('License validation failed', 'INVALID_LICENSE', { suggestion: 'Your subscription may have expired or been cancelled' }),
36
+ API_KEY_MISSING: new TunePromptError('API key not configured', 'API_KEY_MISSING', { suggestion: 'Set ANTHROPIC_API_KEY or OPENAI_API_KEY in your environment' }),
37
+ NO_FAILED_TESTS: new TunePromptError('No failed tests found to fix', 'NO_FAILED_TESTS', { suggestion: 'Run: tuneprompt run first' }),
38
+ NETWORK_ERROR: new TunePromptError('Network request failed', 'NETWORK_ERROR', { suggestion: 'Check your internet connection and try again' }),
39
+ OPTIMIZATION_FAILED: new TunePromptError('Failed to generate prompt optimization', 'OPTIMIZATION_FAILED', { suggestion: 'The AI service may be temporarily unavailable' })
40
+ };
@@ -0,0 +1,40 @@
1
+ interface LicenseData {
2
+ subscriptionId: string;
3
+ email: string;
4
+ plan: 'pro-monthly' | 'pro-yearly' | 'lifetime';
5
+ activatedAt: string;
6
+ lastVerified: string;
7
+ instanceId: string;
8
+ }
9
+ /**
10
+ * Save license data locally
11
+ */
12
+ export declare function saveLicense(data: LicenseData): void;
13
+ /**
14
+ * Load license data from disk
15
+ */
16
+ export declare function loadLicense(): LicenseData | null;
17
+ /**
18
+ * Delete license from disk
19
+ */
20
+ export declare function deleteLicense(): void;
21
+ /**
22
+ * Main license check function
23
+ * Always verifies with backend to ensure real-time status
24
+ */
25
+ export declare function checkLicense(): Promise<boolean>;
26
+ /**
27
+ * Activate a new license
28
+ */
29
+ export declare function activateLicense(subscriptionId: string, email: string, plan: 'pro-monthly' | 'pro-yearly' | 'lifetime'): Promise<boolean>;
30
+ /**
31
+ * Get current license info
32
+ */
33
+ export declare function getLicenseInfo(): LicenseData | null;
34
+ /**
35
+ * License Manager for checking features
36
+ */
37
+ export declare class LicenseManager {
38
+ hasFeature(feature: string): Promise<boolean>;
39
+ }
40
+ export {};
@@ -0,0 +1,207 @@
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.LicenseManager = void 0;
40
+ exports.saveLicense = saveLicense;
41
+ exports.loadLicense = loadLicense;
42
+ exports.deleteLicense = deleteLicense;
43
+ exports.checkLicense = checkLicense;
44
+ exports.activateLicense = activateLicense;
45
+ exports.getLicenseInfo = getLicenseInfo;
46
+ const fs = __importStar(require("fs"));
47
+ const path = __importStar(require("path"));
48
+ const os = __importStar(require("os"));
49
+ const crypto = __importStar(require("crypto"));
50
+ const axios_1 = __importDefault(require("axios"));
51
+ const LICENSE_DIR = path.join(os.homedir(), '.tuneprompt');
52
+ const LICENSE_FILE = path.join(LICENSE_DIR, 'license.json');
53
+ const VERIFICATION_INTERVAL = 24 * 60 * 60 * 1000; // 24 hours
54
+ /**
55
+ * Ensure license directory exists
56
+ */
57
+ function ensureLicenseDir() {
58
+ if (!fs.existsSync(LICENSE_DIR)) {
59
+ fs.mkdirSync(LICENSE_DIR, { recursive: true });
60
+ }
61
+ }
62
+ /**
63
+ * Generate a unique machine ID
64
+ */
65
+ function generateInstanceId() {
66
+ const hostname = os.hostname();
67
+ const username = os.userInfo().username;
68
+ const hash = crypto
69
+ .createHash('sha256')
70
+ .update(`${hostname}-${username}`)
71
+ .digest('hex')
72
+ .substring(0, 16);
73
+ return hash;
74
+ }
75
+ /**
76
+ * Save license data locally
77
+ */
78
+ function saveLicense(data) {
79
+ ensureLicenseDir();
80
+ const encrypted = Buffer.from(JSON.stringify(data)).toString('base64');
81
+ fs.writeFileSync(LICENSE_FILE, encrypted, 'utf-8');
82
+ }
83
+ /**
84
+ * Load license data from disk
85
+ */
86
+ function loadLicense() {
87
+ if (!fs.existsSync(LICENSE_FILE)) {
88
+ return null;
89
+ }
90
+ try {
91
+ const encrypted = fs.readFileSync(LICENSE_FILE, 'utf-8');
92
+ const decrypted = Buffer.from(encrypted, 'base64').toString('utf-8');
93
+ return JSON.parse(decrypted);
94
+ }
95
+ catch (error) {
96
+ console.error('Failed to load license:', error);
97
+ return null;
98
+ }
99
+ }
100
+ /**
101
+ * Delete license from disk
102
+ */
103
+ function deleteLicense() {
104
+ if (fs.existsSync(LICENSE_FILE)) {
105
+ fs.unlinkSync(LICENSE_FILE);
106
+ }
107
+ }
108
+ /**
109
+ * Check if license needs verification (24h since last check)
110
+ */
111
+ function needsVerification(license) {
112
+ const lastVerified = new Date(license.lastVerified).getTime();
113
+ const now = Date.now();
114
+ return (now - lastVerified) > VERIFICATION_INTERVAL;
115
+ }
116
+ /**
117
+ * Verify license with backend API
118
+ */
119
+ async function verifyWithBackend(subscriptionId) {
120
+ try {
121
+ // Call your backend API (we'll create this in Week 3)
122
+ const response = await axios_1.default.post(`${process.env.TUNEPROMPT_API_URL || 'https://api.tuneprompt.com'}/api/verify-license`, {
123
+ subscriptionId
124
+ }, {
125
+ timeout: 5000
126
+ });
127
+ return response.data.valid === true;
128
+ }
129
+ catch (error) {
130
+ // Fail open: if API is down, allow access for paid users
131
+ console.warn('License verification failed (network issue), allowing access');
132
+ return true;
133
+ }
134
+ }
135
+ /**
136
+ * Main license check function
137
+ * Always verifies with backend to ensure real-time status
138
+ */
139
+ async function checkLicense() {
140
+ const license = loadLicense();
141
+ if (!license) {
142
+ return false; // No license found
143
+ }
144
+ // Lifetime licenses don't need verification
145
+ if (license.plan === 'lifetime') {
146
+ return true;
147
+ }
148
+ // Always verify with backend to get real-time status
149
+ const isValid = await verifyWithBackend(license.subscriptionId);
150
+ if (isValid) {
151
+ // Update last verified timestamp
152
+ license.lastVerified = new Date().toISOString();
153
+ saveLicense(license);
154
+ return true;
155
+ }
156
+ else {
157
+ // License is invalid (expired/cancelled)
158
+ deleteLicense();
159
+ return false;
160
+ }
161
+ }
162
+ /**
163
+ * Activate a new license
164
+ */
165
+ async function activateLicense(subscriptionId, email, plan) {
166
+ try {
167
+ // Verify the subscription is valid
168
+ const isValid = await verifyWithBackend(subscriptionId);
169
+ if (!isValid) {
170
+ return false;
171
+ }
172
+ const licenseData = {
173
+ subscriptionId,
174
+ email,
175
+ plan,
176
+ activatedAt: new Date().toISOString(),
177
+ lastVerified: new Date().toISOString(),
178
+ instanceId: generateInstanceId()
179
+ };
180
+ saveLicense(licenseData);
181
+ return true;
182
+ }
183
+ catch (error) {
184
+ console.error('License activation failed:', error);
185
+ return false;
186
+ }
187
+ }
188
+ /**
189
+ * Get current license info
190
+ */
191
+ function getLicenseInfo() {
192
+ return loadLicense();
193
+ }
194
+ /**
195
+ * License Manager for checking features
196
+ */
197
+ class LicenseManager {
198
+ async hasFeature(feature) {
199
+ const isValid = await checkLicense();
200
+ if (!isValid)
201
+ return false;
202
+ // Currently all features are available with any valid license
203
+ // In the future, we can add plan-specific logic here
204
+ return true;
205
+ }
206
+ }
207
+ exports.LicenseManager = LicenseManager;
@@ -0,0 +1,2 @@
1
+ import { FailedTest } from '../types/fix';
2
+ export declare function getFailedTests(): Promise<FailedTest[]>;