@codebakers/cli 2.9.0 → 3.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/dist/commands/billing.d.ts +4 -0
- package/dist/commands/billing.js +91 -0
- package/dist/commands/extend.d.ts +4 -0
- package/dist/commands/extend.js +141 -0
- package/dist/commands/go.d.ts +4 -0
- package/dist/commands/go.js +173 -0
- package/dist/config.d.ts +33 -0
- package/dist/config.js +83 -1
- package/dist/index.js +23 -5
- package/dist/lib/fingerprint.d.ts +23 -0
- package/dist/lib/fingerprint.js +136 -0
- package/dist/mcp/server.js +49 -16
- package/package.json +1 -1
- package/src/commands/billing.ts +99 -0
- package/src/commands/extend.ts +157 -0
- package/src/commands/go.ts +197 -0
- package/src/config.ts +101 -1
- package/src/index.ts +26 -5
- package/src/lib/fingerprint.ts +122 -0
- package/src/mcp/server.ts +60 -17
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getDeviceFingerprint = getDeviceFingerprint;
|
|
37
|
+
exports.canCreateFingerprint = canCreateFingerprint;
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const crypto = __importStar(require("crypto"));
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
/**
|
|
42
|
+
* Get a stable machine identifier based on OS
|
|
43
|
+
* - Windows: MachineGuid from registry
|
|
44
|
+
* - macOS: IOPlatformUUID from system
|
|
45
|
+
* - Linux: /etc/machine-id
|
|
46
|
+
*/
|
|
47
|
+
function getMachineId() {
|
|
48
|
+
try {
|
|
49
|
+
const platform = os.platform();
|
|
50
|
+
if (platform === 'win32') {
|
|
51
|
+
// Windows: Use MachineGuid from registry
|
|
52
|
+
const output = (0, child_process_1.execSync)('reg query "HKLM\\SOFTWARE\\Microsoft\\Cryptography" /v MachineGuid', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
53
|
+
const match = output.match(/MachineGuid\s+REG_SZ\s+(.+)/);
|
|
54
|
+
if (match && match[1]) {
|
|
55
|
+
return match[1].trim();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else if (platform === 'darwin') {
|
|
59
|
+
// macOS: Use hardware UUID
|
|
60
|
+
const output = (0, child_process_1.execSync)('ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
61
|
+
const match = output.match(/"IOPlatformUUID"\s*=\s*"(.+)"/);
|
|
62
|
+
if (match && match[1]) {
|
|
63
|
+
return match[1];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// Linux: Use machine-id
|
|
68
|
+
const output = (0, child_process_1.execSync)('cat /etc/machine-id', {
|
|
69
|
+
encoding: 'utf-8',
|
|
70
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
71
|
+
});
|
|
72
|
+
return output.trim();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Fallback handled below
|
|
77
|
+
}
|
|
78
|
+
// Fallback: Create a stable hash from hostname + username + home directory
|
|
79
|
+
// This is less reliable but works when we can't access system IDs
|
|
80
|
+
const fallbackData = [
|
|
81
|
+
os.hostname(),
|
|
82
|
+
os.userInfo().username,
|
|
83
|
+
os.homedir(),
|
|
84
|
+
os.platform(),
|
|
85
|
+
os.arch(),
|
|
86
|
+
].join('|');
|
|
87
|
+
return crypto.createHash('sha256').update(fallbackData).digest('hex').slice(0, 36);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get a complete device fingerprint
|
|
91
|
+
* The deviceHash is the primary identifier used for trial tracking
|
|
92
|
+
*/
|
|
93
|
+
function getDeviceFingerprint() {
|
|
94
|
+
const machineId = getMachineId();
|
|
95
|
+
// Collect stable machine characteristics
|
|
96
|
+
const fingerprintData = {
|
|
97
|
+
machineId,
|
|
98
|
+
hostname: os.hostname(),
|
|
99
|
+
username: os.userInfo().username,
|
|
100
|
+
platform: os.platform(),
|
|
101
|
+
arch: os.arch(),
|
|
102
|
+
cpuModel: os.cpus()[0]?.model || 'unknown',
|
|
103
|
+
totalMemory: Math.floor(os.totalmem() / (1024 * 1024 * 1024)), // GB rounded
|
|
104
|
+
homeDir: os.homedir(),
|
|
105
|
+
};
|
|
106
|
+
// Create a stable hash from all characteristics
|
|
107
|
+
const deviceHash = crypto
|
|
108
|
+
.createHash('sha256')
|
|
109
|
+
.update(JSON.stringify(fingerprintData))
|
|
110
|
+
.digest('hex');
|
|
111
|
+
return {
|
|
112
|
+
machineId,
|
|
113
|
+
deviceHash,
|
|
114
|
+
platform: os.platform(),
|
|
115
|
+
hostname: os.hostname(),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Validate that we can create a fingerprint
|
|
120
|
+
* Used for diagnostics
|
|
121
|
+
*/
|
|
122
|
+
function canCreateFingerprint() {
|
|
123
|
+
try {
|
|
124
|
+
const fp = getDeviceFingerprint();
|
|
125
|
+
if (fp.deviceHash && fp.deviceHash.length === 64) {
|
|
126
|
+
return { success: true };
|
|
127
|
+
}
|
|
128
|
+
return { success: false, error: 'Invalid fingerprint generated' };
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -53,11 +53,15 @@ class CodeBakersServer {
|
|
|
53
53
|
server;
|
|
54
54
|
apiKey;
|
|
55
55
|
apiUrl;
|
|
56
|
+
trialState;
|
|
57
|
+
authMode;
|
|
56
58
|
autoUpdateChecked = false;
|
|
57
59
|
autoUpdateInProgress = false;
|
|
58
60
|
constructor() {
|
|
59
61
|
this.apiKey = (0, config_js_1.getApiKey)();
|
|
60
62
|
this.apiUrl = (0, config_js_1.getApiUrl)();
|
|
63
|
+
this.trialState = (0, config_js_1.getTrialState)();
|
|
64
|
+
this.authMode = (0, config_js_1.getAuthMode)();
|
|
61
65
|
this.server = new index_js_1.Server({
|
|
62
66
|
name: 'codebakers',
|
|
63
67
|
version: '1.0.0',
|
|
@@ -72,12 +76,25 @@ class CodeBakersServer {
|
|
|
72
76
|
// Silently ignore errors - don't interrupt user
|
|
73
77
|
});
|
|
74
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Get authorization headers for API requests
|
|
81
|
+
* Supports both API key (paid users) and trial ID (free users)
|
|
82
|
+
*/
|
|
83
|
+
getAuthHeaders() {
|
|
84
|
+
if (this.apiKey) {
|
|
85
|
+
return { 'Authorization': `Bearer ${this.apiKey}` };
|
|
86
|
+
}
|
|
87
|
+
if (this.trialState?.trialId) {
|
|
88
|
+
return { 'X-Trial-Id': this.trialState.trialId };
|
|
89
|
+
}
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
75
92
|
/**
|
|
76
93
|
* Automatically check for and apply pattern updates
|
|
77
94
|
* Runs silently in background - no user intervention needed
|
|
78
95
|
*/
|
|
79
96
|
async checkAndAutoUpdate() {
|
|
80
|
-
if (this.autoUpdateChecked || this.autoUpdateInProgress ||
|
|
97
|
+
if (this.autoUpdateChecked || this.autoUpdateInProgress || this.authMode === 'none') {
|
|
81
98
|
return;
|
|
82
99
|
}
|
|
83
100
|
this.autoUpdateInProgress = true;
|
|
@@ -110,7 +127,7 @@ class CodeBakersServer {
|
|
|
110
127
|
}
|
|
111
128
|
// Fetch latest version
|
|
112
129
|
const response = await fetch(`${this.apiUrl}/api/content/version`, {
|
|
113
|
-
headers:
|
|
130
|
+
headers: this.getAuthHeaders(),
|
|
114
131
|
});
|
|
115
132
|
if (!response.ok) {
|
|
116
133
|
this.autoUpdateInProgress = false;
|
|
@@ -128,7 +145,7 @@ class CodeBakersServer {
|
|
|
128
145
|
}
|
|
129
146
|
// Fetch full content and update
|
|
130
147
|
const contentResponse = await fetch(`${this.apiUrl}/api/content`, {
|
|
131
|
-
headers:
|
|
148
|
+
headers: this.getAuthHeaders(),
|
|
132
149
|
});
|
|
133
150
|
if (!contentResponse.ok) {
|
|
134
151
|
this.autoUpdateInProgress = false;
|
|
@@ -362,7 +379,7 @@ class CodeBakersServer {
|
|
|
362
379
|
let latest = null;
|
|
363
380
|
try {
|
|
364
381
|
const response = await fetch(`${this.apiUrl}/api/content/version`, {
|
|
365
|
-
headers: this.
|
|
382
|
+
headers: this.getAuthHeaders(),
|
|
366
383
|
});
|
|
367
384
|
if (response.ok) {
|
|
368
385
|
latest = await response.json();
|
|
@@ -805,8 +822,26 @@ class CodeBakersServer {
|
|
|
805
822
|
}));
|
|
806
823
|
// Handle tool calls
|
|
807
824
|
this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
808
|
-
|
|
809
|
-
|
|
825
|
+
// Check access: API key OR valid trial
|
|
826
|
+
if (this.authMode === 'none') {
|
|
827
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, 'Not logged in. Run `codebakers go` to start a free trial, or `codebakers setup` if you have an account.');
|
|
828
|
+
}
|
|
829
|
+
// Check if trial expired
|
|
830
|
+
if (this.authMode === 'trial' && (0, config_js_1.isTrialExpired)()) {
|
|
831
|
+
const trialState = (0, config_js_1.getTrialState)();
|
|
832
|
+
if (trialState?.stage === 'anonymous') {
|
|
833
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, 'Trial expired. Run `codebakers extend` to add 7 more days with GitHub, or `codebakers billing` to upgrade.');
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, 'Trial expired. Run `codebakers billing` to upgrade to a paid plan.');
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
// Show warning if trial expiring soon
|
|
840
|
+
if (this.authMode === 'trial') {
|
|
841
|
+
const daysRemaining = (0, config_js_1.getTrialDaysRemaining)();
|
|
842
|
+
if (daysRemaining <= 2) {
|
|
843
|
+
console.error(`[CodeBakers] Trial expires in ${daysRemaining} day${daysRemaining !== 1 ? 's' : ''}. Run 'codebakers extend' or 'codebakers billing'.`);
|
|
844
|
+
}
|
|
810
845
|
}
|
|
811
846
|
const { name, arguments: args } = request.params;
|
|
812
847
|
switch (name) {
|
|
@@ -872,7 +907,7 @@ class CodeBakersServer {
|
|
|
872
907
|
method: 'POST',
|
|
873
908
|
headers: {
|
|
874
909
|
'Content-Type': 'application/json',
|
|
875
|
-
|
|
910
|
+
...this.getAuthHeaders(),
|
|
876
911
|
},
|
|
877
912
|
body: JSON.stringify({
|
|
878
913
|
prompt: userRequest,
|
|
@@ -981,9 +1016,7 @@ Show the user what their simple request was expanded into, then proceed with the
|
|
|
981
1016
|
async handleListPatterns() {
|
|
982
1017
|
const response = await fetch(`${this.apiUrl}/api/patterns`, {
|
|
983
1018
|
method: 'GET',
|
|
984
|
-
headers:
|
|
985
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
986
|
-
},
|
|
1019
|
+
headers: this.getAuthHeaders(),
|
|
987
1020
|
});
|
|
988
1021
|
if (!response.ok) {
|
|
989
1022
|
const error = await response.json().catch(() => ({}));
|
|
@@ -1026,7 +1059,7 @@ Show the user what their simple request was expanded into, then proceed with the
|
|
|
1026
1059
|
method: 'POST',
|
|
1027
1060
|
headers: {
|
|
1028
1061
|
'Content-Type': 'application/json',
|
|
1029
|
-
|
|
1062
|
+
...this.getAuthHeaders(),
|
|
1030
1063
|
},
|
|
1031
1064
|
body: JSON.stringify({ patterns }),
|
|
1032
1065
|
});
|
|
@@ -1043,7 +1076,7 @@ Show the user what their simple request was expanded into, then proceed with the
|
|
|
1043
1076
|
method: 'POST',
|
|
1044
1077
|
headers: {
|
|
1045
1078
|
'Content-Type': 'application/json',
|
|
1046
|
-
|
|
1079
|
+
...this.getAuthHeaders(),
|
|
1047
1080
|
},
|
|
1048
1081
|
body: JSON.stringify({ query }),
|
|
1049
1082
|
});
|
|
@@ -1291,7 +1324,7 @@ Show the user what their simple request was expanded into, then proceed with the
|
|
|
1291
1324
|
results.push('\n## Installing CodeBakers Patterns...\n');
|
|
1292
1325
|
const response = await fetch(`${this.apiUrl}/api/content`, {
|
|
1293
1326
|
method: 'GET',
|
|
1294
|
-
headers:
|
|
1327
|
+
headers: this.getAuthHeaders(),
|
|
1295
1328
|
});
|
|
1296
1329
|
if (response.ok) {
|
|
1297
1330
|
const content = await response.json();
|
|
@@ -1671,7 +1704,7 @@ Or if user declines, call without fullDeploy:
|
|
|
1671
1704
|
try {
|
|
1672
1705
|
const response = await fetch(`${this.apiUrl}/api/content`, {
|
|
1673
1706
|
method: 'GET',
|
|
1674
|
-
headers:
|
|
1707
|
+
headers: this.getAuthHeaders(),
|
|
1675
1708
|
});
|
|
1676
1709
|
if (!response.ok) {
|
|
1677
1710
|
throw new Error('Failed to fetch patterns from API');
|
|
@@ -2678,7 +2711,7 @@ Just describe what you want to build! I'll automatically:
|
|
|
2678
2711
|
method: 'POST',
|
|
2679
2712
|
headers: {
|
|
2680
2713
|
'Content-Type': 'application/json',
|
|
2681
|
-
|
|
2714
|
+
...this.getAuthHeaders(),
|
|
2682
2715
|
},
|
|
2683
2716
|
body: JSON.stringify({
|
|
2684
2717
|
category,
|
|
@@ -2725,7 +2758,7 @@ Just describe what you want to build! I'll automatically:
|
|
|
2725
2758
|
method: 'POST',
|
|
2726
2759
|
headers: {
|
|
2727
2760
|
'Content-Type': 'application/json',
|
|
2728
|
-
|
|
2761
|
+
...this.getAuthHeaders(),
|
|
2729
2762
|
},
|
|
2730
2763
|
body: JSON.stringify({
|
|
2731
2764
|
eventType,
|
package/package.json
CHANGED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { getApiUrl, getApiKey, getTrialState, isTrialExpired, getTrialDaysRemaining } from '../config.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Open a URL in the default browser
|
|
7
|
+
*/
|
|
8
|
+
function openBrowser(url: string): void {
|
|
9
|
+
const platform = process.platform;
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
if (platform === 'win32') {
|
|
13
|
+
execSync(`start "" "${url}"`, { stdio: 'ignore', shell: 'cmd.exe' });
|
|
14
|
+
} else if (platform === 'darwin') {
|
|
15
|
+
execSync(`open "${url}"`, { stdio: 'ignore' });
|
|
16
|
+
} else {
|
|
17
|
+
execSync(`xdg-open "${url}" || sensible-browser "${url}" || x-www-browser "${url}"`, {
|
|
18
|
+
stdio: 'ignore',
|
|
19
|
+
shell: '/bin/sh',
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
console.log(chalk.yellow(`\n Could not open browser automatically.`));
|
|
24
|
+
console.log(chalk.gray(` Please open this URL manually:\n`));
|
|
25
|
+
console.log(chalk.cyan(` ${url}\n`));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Open billing page for subscription management
|
|
31
|
+
*/
|
|
32
|
+
export async function billing(): Promise<void> {
|
|
33
|
+
console.log(chalk.blue(`
|
|
34
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
35
|
+
║ ║
|
|
36
|
+
║ ${chalk.bold.white('CodeBakers Billing & Subscription')} ║
|
|
37
|
+
║ ║
|
|
38
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
39
|
+
`));
|
|
40
|
+
|
|
41
|
+
// Check current status
|
|
42
|
+
const apiKey = getApiKey();
|
|
43
|
+
const trial = getTrialState();
|
|
44
|
+
|
|
45
|
+
if (apiKey) {
|
|
46
|
+
console.log(chalk.green(' ✓ You have an active subscription\n'));
|
|
47
|
+
console.log(chalk.gray(' Opening settings page to manage your subscription...\n'));
|
|
48
|
+
|
|
49
|
+
const apiUrl = getApiUrl();
|
|
50
|
+
const settingsUrl = `${apiUrl}/settings`;
|
|
51
|
+
|
|
52
|
+
openBrowser(settingsUrl);
|
|
53
|
+
console.log(chalk.gray(` ${settingsUrl}\n`));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (trial) {
|
|
58
|
+
if (isTrialExpired()) {
|
|
59
|
+
console.log(chalk.yellow(' ⚠️ Your trial has expired\n'));
|
|
60
|
+
} else {
|
|
61
|
+
const daysRemaining = getTrialDaysRemaining();
|
|
62
|
+
console.log(chalk.gray(` Trial: ${daysRemaining} day${daysRemaining !== 1 ? 's' : ''} remaining\n`));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Show pricing
|
|
67
|
+
console.log(chalk.white(' Choose your plan:\n'));
|
|
68
|
+
|
|
69
|
+
console.log(chalk.cyan(' Pro ') + chalk.white('- $49/month ') + chalk.gray('(1 seat)'));
|
|
70
|
+
console.log(chalk.gray(' All 40 pattern modules'));
|
|
71
|
+
console.log(chalk.gray(' Unlimited projects'));
|
|
72
|
+
console.log(chalk.gray(' Priority support\n'));
|
|
73
|
+
|
|
74
|
+
console.log(chalk.cyan(' Team ') + chalk.white('- $149/month ') + chalk.gray('(5 seats)'));
|
|
75
|
+
console.log(chalk.gray(' Everything in Pro'));
|
|
76
|
+
console.log(chalk.gray(' Team collaboration'));
|
|
77
|
+
console.log(chalk.gray(' Shared API keys\n'));
|
|
78
|
+
|
|
79
|
+
console.log(chalk.cyan(' Agency ') + chalk.white('- $349/month ') + chalk.gray('(unlimited seats)'));
|
|
80
|
+
console.log(chalk.gray(' Everything in Team'));
|
|
81
|
+
console.log(chalk.gray(' White-label option'));
|
|
82
|
+
console.log(chalk.gray(' Dedicated support\n'));
|
|
83
|
+
|
|
84
|
+
console.log(chalk.gray(' Enterprise pricing available for large teams.\n'));
|
|
85
|
+
|
|
86
|
+
// Open billing page
|
|
87
|
+
const apiUrl = getApiUrl();
|
|
88
|
+
const billingUrl = `${apiUrl}/billing`;
|
|
89
|
+
|
|
90
|
+
console.log(chalk.white(' Opening billing page...\n'));
|
|
91
|
+
|
|
92
|
+
openBrowser(billingUrl);
|
|
93
|
+
|
|
94
|
+
console.log(chalk.gray(` ${billingUrl}\n`));
|
|
95
|
+
|
|
96
|
+
console.log(chalk.gray(' After subscribing, run:'));
|
|
97
|
+
console.log(chalk.cyan(' codebakers setup\n'));
|
|
98
|
+
console.log(chalk.gray(' to configure your API key.\n'));
|
|
99
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import {
|
|
5
|
+
getTrialState,
|
|
6
|
+
setTrialState,
|
|
7
|
+
getApiUrl,
|
|
8
|
+
getApiKey,
|
|
9
|
+
isTrialExpired,
|
|
10
|
+
type TrialState,
|
|
11
|
+
} from '../config.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Open a URL in the default browser
|
|
15
|
+
*/
|
|
16
|
+
function openBrowser(url: string): void {
|
|
17
|
+
const platform = process.platform;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
if (platform === 'win32') {
|
|
21
|
+
execSync(`start "" "${url}"`, { stdio: 'ignore', shell: 'cmd.exe' });
|
|
22
|
+
} else if (platform === 'darwin') {
|
|
23
|
+
execSync(`open "${url}"`, { stdio: 'ignore' });
|
|
24
|
+
} else {
|
|
25
|
+
// Linux - try common browsers
|
|
26
|
+
execSync(`xdg-open "${url}" || sensible-browser "${url}" || x-www-browser "${url}"`, {
|
|
27
|
+
stdio: 'ignore',
|
|
28
|
+
shell: '/bin/sh',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
console.log(chalk.yellow(`\n Could not open browser automatically.`));
|
|
33
|
+
console.log(chalk.gray(` Please open this URL manually:\n`));
|
|
34
|
+
console.log(chalk.cyan(` ${url}\n`));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Sleep for a specified number of milliseconds
|
|
40
|
+
*/
|
|
41
|
+
function sleep(ms: number): Promise<void> {
|
|
42
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Extend trial by connecting GitHub account
|
|
47
|
+
*/
|
|
48
|
+
export async function extend(): Promise<void> {
|
|
49
|
+
console.log(chalk.blue(`
|
|
50
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
51
|
+
║ ║
|
|
52
|
+
║ ${chalk.bold.white('Extend Your Trial with GitHub')} ║
|
|
53
|
+
║ ║
|
|
54
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
55
|
+
`));
|
|
56
|
+
|
|
57
|
+
// Check if user already has an API key (paid user)
|
|
58
|
+
const apiKey = getApiKey();
|
|
59
|
+
if (apiKey) {
|
|
60
|
+
console.log(chalk.green(' ✓ You\'re already logged in with an API key!\n'));
|
|
61
|
+
console.log(chalk.gray(' You have unlimited access. No extension needed.\n'));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check for existing trial
|
|
66
|
+
const trial = getTrialState();
|
|
67
|
+
|
|
68
|
+
if (!trial) {
|
|
69
|
+
console.log(chalk.yellow(' No trial found.\n'));
|
|
70
|
+
console.log(chalk.white(' Start your free trial first:\n'));
|
|
71
|
+
console.log(chalk.cyan(' codebakers go\n'));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check if already extended
|
|
76
|
+
if (trial.stage === 'extended') {
|
|
77
|
+
console.log(chalk.yellow(' Your trial has already been extended.\n'));
|
|
78
|
+
|
|
79
|
+
if (isTrialExpired()) {
|
|
80
|
+
console.log(chalk.white(' Ready to upgrade? $49/month for unlimited access:\n'));
|
|
81
|
+
console.log(chalk.cyan(' codebakers upgrade\n'));
|
|
82
|
+
} else {
|
|
83
|
+
const expiresAt = new Date(trial.expiresAt);
|
|
84
|
+
const daysRemaining = Math.max(0, Math.ceil((expiresAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24)));
|
|
85
|
+
console.log(chalk.gray(` ${daysRemaining} day${daysRemaining !== 1 ? 's' : ''} remaining.\n`));
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check if converted
|
|
91
|
+
if (trial.stage === 'converted') {
|
|
92
|
+
console.log(chalk.green(' ✓ You\'ve upgraded to a paid plan!\n'));
|
|
93
|
+
console.log(chalk.gray(' Run ') + chalk.cyan('codebakers setup') + chalk.gray(' to configure your API key.\n'));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Open browser for GitHub OAuth
|
|
98
|
+
const apiUrl = getApiUrl();
|
|
99
|
+
const authUrl = `${apiUrl}/api/auth/github?trial_id=${trial.trialId}`;
|
|
100
|
+
|
|
101
|
+
console.log(chalk.white(' Opening browser for GitHub authorization...\n'));
|
|
102
|
+
|
|
103
|
+
openBrowser(authUrl);
|
|
104
|
+
|
|
105
|
+
console.log(chalk.gray(' Waiting for authorization...'));
|
|
106
|
+
console.log(chalk.gray(' (This may take a moment)\n'));
|
|
107
|
+
|
|
108
|
+
// Poll for completion
|
|
109
|
+
const spinner = ora('Checking authorization status...').start();
|
|
110
|
+
let extended = false;
|
|
111
|
+
let pollCount = 0;
|
|
112
|
+
const maxPolls = 60; // 2 minutes max
|
|
113
|
+
|
|
114
|
+
while (pollCount < maxPolls && !extended) {
|
|
115
|
+
await sleep(2000);
|
|
116
|
+
pollCount++;
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const response = await fetch(`${apiUrl}/api/trial/status?trialId=${trial.trialId}`);
|
|
120
|
+
const data = await response.json();
|
|
121
|
+
|
|
122
|
+
if (data.stage === 'extended') {
|
|
123
|
+
extended = true;
|
|
124
|
+
|
|
125
|
+
// Update local trial state
|
|
126
|
+
const updatedTrial: TrialState = {
|
|
127
|
+
...trial,
|
|
128
|
+
stage: 'extended',
|
|
129
|
+
expiresAt: data.expiresAt,
|
|
130
|
+
extendedAt: new Date().toISOString(),
|
|
131
|
+
...(data.githubUsername && { githubUsername: data.githubUsername }),
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
setTrialState(updatedTrial);
|
|
135
|
+
spinner.succeed('Trial extended!');
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
// Ignore polling errors
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (extended) {
|
|
143
|
+
console.log(chalk.green(`
|
|
144
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
145
|
+
║ ✅ Trial Extended! ║
|
|
146
|
+
║ ║
|
|
147
|
+
║ ${chalk.white('You have 7 more days to build with CodeBakers.')} ║
|
|
148
|
+
║ ║
|
|
149
|
+
║ ${chalk.gray('Keep building - your project is waiting!')} ║
|
|
150
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
151
|
+
`));
|
|
152
|
+
} else {
|
|
153
|
+
spinner.warn('Authorization timed out');
|
|
154
|
+
console.log(chalk.yellow('\n Please try again or authorize manually:\n'));
|
|
155
|
+
console.log(chalk.cyan(` ${authUrl}\n`));
|
|
156
|
+
}
|
|
157
|
+
}
|