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.
- package/LICENSE +21 -0
- package/README.md +151 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +146 -0
- package/dist/commands/activate.d.ts +1 -0
- package/dist/commands/activate.js +91 -0
- package/dist/commands/fix.d.ts +1 -0
- package/dist/commands/fix.js +187 -0
- package/dist/commands/history.d.ts +5 -0
- package/dist/commands/history.js +63 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +96 -0
- package/dist/commands/run.d.ts +9 -0
- package/dist/commands/run.js +216 -0
- package/dist/db/migrate.d.ts +2 -0
- package/dist/db/migrate.js +8 -0
- package/dist/engine/constraintExtractor.d.ts +8 -0
- package/dist/engine/constraintExtractor.js +54 -0
- package/dist/engine/loader.d.ts +5 -0
- package/dist/engine/loader.js +74 -0
- package/dist/engine/metaPrompt.d.ts +11 -0
- package/dist/engine/metaPrompt.js +129 -0
- package/dist/engine/optimizer.d.ts +26 -0
- package/dist/engine/optimizer.js +246 -0
- package/dist/engine/reporter.d.ts +7 -0
- package/dist/engine/reporter.js +58 -0
- package/dist/engine/runner.d.ts +9 -0
- package/dist/engine/runner.js +169 -0
- package/dist/engine/shadowTester.d.ts +11 -0
- package/dist/engine/shadowTester.js +156 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +26 -0
- package/dist/providers/anthropic.d.ts +12 -0
- package/dist/providers/anthropic.js +51 -0
- package/dist/providers/base.d.ts +15 -0
- package/dist/providers/base.js +10 -0
- package/dist/providers/openai.d.ts +12 -0
- package/dist/providers/openai.js +58 -0
- package/dist/providers/openrouter.d.ts +11 -0
- package/dist/providers/openrouter.js +83 -0
- package/dist/scoring/exact-match.d.ts +1 -0
- package/dist/scoring/exact-match.js +8 -0
- package/dist/scoring/json-validator.d.ts +4 -0
- package/dist/scoring/json-validator.js +29 -0
- package/dist/scoring/semantic.d.ts +8 -0
- package/dist/scoring/semantic.js +107 -0
- package/dist/services/cloud.service.d.ts +49 -0
- package/dist/services/cloud.service.js +82 -0
- package/dist/storage/database.d.ts +10 -0
- package/dist/storage/database.js +179 -0
- package/dist/types/fix.d.ts +28 -0
- package/dist/types/fix.js +2 -0
- package/dist/types/index.d.ts +58 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/analytics.d.ts +2 -0
- package/dist/utils/analytics.js +22 -0
- package/dist/utils/config.d.ts +3 -0
- package/dist/utils/config.js +70 -0
- package/dist/utils/errorHandler.d.ts +14 -0
- package/dist/utils/errorHandler.js +40 -0
- package/dist/utils/license.d.ts +40 -0
- package/dist/utils/license.js +207 -0
- package/dist/utils/storage.d.ts +2 -0
- package/dist/utils/storage.js +25 -0
- 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,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,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,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;
|