@codebakers/cli 2.9.0 → 3.0.1
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 +328 -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 +1319 -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 +386 -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 +1524 -17
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
|
+
}
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { execSync, spawn } from 'child_process';
|
|
4
|
+
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { createInterface } from 'readline';
|
|
7
|
+
import {
|
|
8
|
+
getTrialState,
|
|
9
|
+
setTrialState,
|
|
10
|
+
getApiUrl,
|
|
11
|
+
getApiKey,
|
|
12
|
+
isTrialExpired,
|
|
13
|
+
getTrialDaysRemaining,
|
|
14
|
+
type TrialState,
|
|
15
|
+
} from '../config.js';
|
|
16
|
+
import { getDeviceFingerprint } from '../lib/fingerprint.js';
|
|
17
|
+
|
|
18
|
+
function prompt(question: string): Promise<string> {
|
|
19
|
+
const rl = createInterface({
|
|
20
|
+
input: process.stdin,
|
|
21
|
+
output: process.stdout,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
rl.question(question, (answer) => {
|
|
26
|
+
rl.close();
|
|
27
|
+
resolve(answer.trim().toLowerCase());
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ContentResponse {
|
|
33
|
+
version: string;
|
|
34
|
+
router: string;
|
|
35
|
+
modules: Record<string, string>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Zero-friction entry point - start using CodeBakers instantly
|
|
40
|
+
*/
|
|
41
|
+
export async function go(): Promise<void> {
|
|
42
|
+
console.log(chalk.blue(`
|
|
43
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
44
|
+
║ ║
|
|
45
|
+
║ ${chalk.bold.white('CodeBakers - Zero Setup Required')} ║
|
|
46
|
+
║ ║
|
|
47
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
48
|
+
`));
|
|
49
|
+
|
|
50
|
+
// Check if user already has an API key (paid user)
|
|
51
|
+
const apiKey = getApiKey();
|
|
52
|
+
if (apiKey) {
|
|
53
|
+
console.log(chalk.green(' ✓ You\'re already logged in with an API key!\n'));
|
|
54
|
+
|
|
55
|
+
// Still install patterns if not already installed
|
|
56
|
+
await installPatternsWithApiKey(apiKey);
|
|
57
|
+
await configureMCP();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check existing trial
|
|
62
|
+
const existingTrial = getTrialState();
|
|
63
|
+
|
|
64
|
+
if (existingTrial && !isTrialExpired()) {
|
|
65
|
+
const daysRemaining = getTrialDaysRemaining();
|
|
66
|
+
console.log(chalk.green(` ✓ Trial active (${daysRemaining} day${daysRemaining !== 1 ? 's' : ''} remaining)\n`));
|
|
67
|
+
|
|
68
|
+
if (existingTrial.stage === 'anonymous' && daysRemaining <= 2) {
|
|
69
|
+
console.log(chalk.yellow(' ⚠️ Trial expiring soon! Extend with GitHub:\n'));
|
|
70
|
+
console.log(chalk.cyan(' codebakers extend\n'));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Install patterns if not already installed
|
|
74
|
+
await installPatterns(existingTrial.trialId);
|
|
75
|
+
|
|
76
|
+
await configureMCP();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check if trial expired
|
|
81
|
+
if (existingTrial && isTrialExpired()) {
|
|
82
|
+
console.log(chalk.yellow(' ⚠️ Your trial has expired.\n'));
|
|
83
|
+
|
|
84
|
+
if (existingTrial.stage === 'anonymous') {
|
|
85
|
+
console.log(chalk.white(' Extend your trial for 7 more days with GitHub:\n'));
|
|
86
|
+
console.log(chalk.cyan(' codebakers extend\n'));
|
|
87
|
+
console.log(chalk.gray(' Or upgrade to Pro ($49/month):\n'));
|
|
88
|
+
console.log(chalk.cyan(' codebakers upgrade\n'));
|
|
89
|
+
} else {
|
|
90
|
+
console.log(chalk.white(' Ready to upgrade? $49/month for unlimited access:\n'));
|
|
91
|
+
console.log(chalk.cyan(' codebakers upgrade\n'));
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Start new trial
|
|
97
|
+
const spinner = ora('Starting your free trial...').start();
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const fingerprint = getDeviceFingerprint();
|
|
101
|
+
const apiUrl = getApiUrl();
|
|
102
|
+
|
|
103
|
+
const response = await fetch(`${apiUrl}/api/trial/start`, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: { 'Content-Type': 'application/json' },
|
|
106
|
+
body: JSON.stringify({
|
|
107
|
+
deviceHash: fingerprint.deviceHash,
|
|
108
|
+
machineId: fingerprint.machineId,
|
|
109
|
+
platform: fingerprint.platform,
|
|
110
|
+
hostname: fingerprint.hostname,
|
|
111
|
+
}),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const data = await response.json();
|
|
115
|
+
|
|
116
|
+
if (data.error === 'trial_not_available') {
|
|
117
|
+
spinner.fail('Trial not available');
|
|
118
|
+
console.log(chalk.yellow(`
|
|
119
|
+
It looks like you've already used a CodeBakers trial.
|
|
120
|
+
|
|
121
|
+
Ready to upgrade? $49/month for unlimited access.
|
|
122
|
+
|
|
123
|
+
${chalk.cyan('codebakers upgrade')} or visit ${chalk.underline('https://codebakers.ai/pricing')}
|
|
124
|
+
`));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
throw new Error(data.error || 'Failed to start trial');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check if returning existing trial
|
|
133
|
+
if (data.stage === 'expired') {
|
|
134
|
+
spinner.warn('Your previous trial has expired');
|
|
135
|
+
console.log('');
|
|
136
|
+
|
|
137
|
+
if (data.canExtend) {
|
|
138
|
+
console.log(chalk.white(' Extend your trial for 7 more days with GitHub:\n'));
|
|
139
|
+
console.log(chalk.cyan(' codebakers extend\n'));
|
|
140
|
+
} else {
|
|
141
|
+
console.log(chalk.white(' Ready to upgrade? $49/month for unlimited access:\n'));
|
|
142
|
+
console.log(chalk.cyan(' codebakers upgrade\n'));
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Save trial state
|
|
148
|
+
const trialState: TrialState = {
|
|
149
|
+
trialId: data.trialId,
|
|
150
|
+
stage: data.stage,
|
|
151
|
+
deviceHash: fingerprint.deviceHash,
|
|
152
|
+
expiresAt: data.expiresAt,
|
|
153
|
+
startedAt: data.startedAt,
|
|
154
|
+
...(data.githubUsername && { githubUsername: data.githubUsername }),
|
|
155
|
+
...(data.projectId && { projectId: data.projectId }),
|
|
156
|
+
...(data.projectName && { projectName: data.projectName }),
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
setTrialState(trialState);
|
|
160
|
+
|
|
161
|
+
spinner.succeed(`Trial started (${data.daysRemaining} days free)`);
|
|
162
|
+
console.log('');
|
|
163
|
+
|
|
164
|
+
// Install patterns (CLAUDE.md and .claude/)
|
|
165
|
+
await installPatterns(data.trialId);
|
|
166
|
+
|
|
167
|
+
// Configure MCP
|
|
168
|
+
await configureMCP();
|
|
169
|
+
|
|
170
|
+
// Show success message
|
|
171
|
+
console.log(chalk.green(`
|
|
172
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
173
|
+
║ ✅ CodeBakers is ready! ║
|
|
174
|
+
║ ║
|
|
175
|
+
║ ${chalk.white('Your 7-day free trial has started.')} ║
|
|
176
|
+
║ ║
|
|
177
|
+
║ ${chalk.gray('Try: "Build me a todo app with authentication"')} ║
|
|
178
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
179
|
+
`));
|
|
180
|
+
|
|
181
|
+
// Attempt auto-restart Claude Code
|
|
182
|
+
await attemptAutoRestart();
|
|
183
|
+
|
|
184
|
+
} catch (error) {
|
|
185
|
+
spinner.fail('Failed to start trial');
|
|
186
|
+
|
|
187
|
+
if (error instanceof Error) {
|
|
188
|
+
if (error.message.includes('fetch') || error.message.includes('network')) {
|
|
189
|
+
console.log(chalk.red('\n Could not connect to CodeBakers server.'));
|
|
190
|
+
console.log(chalk.gray(' Check your internet connection and try again.\n'));
|
|
191
|
+
} else {
|
|
192
|
+
console.log(chalk.red(`\n ${error.message}\n`));
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
console.log(chalk.red('\n An unexpected error occurred.\n'));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function configureMCP(): Promise<void> {
|
|
201
|
+
const spinner = ora('Configuring Claude Code integration...').start();
|
|
202
|
+
const isWindows = process.platform === 'win32';
|
|
203
|
+
|
|
204
|
+
const mcpCmd = isWindows
|
|
205
|
+
? 'claude mcp add --transport stdio codebakers -- cmd /c npx -y @codebakers/cli serve'
|
|
206
|
+
: 'claude mcp add --transport stdio codebakers -- npx -y @codebakers/cli serve';
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
execSync(mcpCmd, { stdio: 'pipe' });
|
|
210
|
+
spinner.succeed('CodeBakers connected to Claude Code');
|
|
211
|
+
} catch (error) {
|
|
212
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
213
|
+
if (errorMessage.includes('already exists') || errorMessage.includes('already registered')) {
|
|
214
|
+
spinner.succeed('CodeBakers already connected to Claude Code');
|
|
215
|
+
} else {
|
|
216
|
+
spinner.warn('Could not auto-configure Claude Code');
|
|
217
|
+
console.log(chalk.gray('\n Run this command manually:\n'));
|
|
218
|
+
console.log(chalk.cyan(` ${mcpCmd}\n`));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function attemptAutoRestart(): Promise<void> {
|
|
224
|
+
const cwd = process.cwd();
|
|
225
|
+
|
|
226
|
+
console.log(chalk.yellow('\n ⚠️ RESTART REQUIRED\n'));
|
|
227
|
+
console.log(chalk.gray(' Claude Code needs to restart to load CodeBakers.\n'));
|
|
228
|
+
|
|
229
|
+
const answer = await prompt(chalk.cyan(' Restart Claude Code now? (Y/n): '));
|
|
230
|
+
|
|
231
|
+
if (answer === 'n' || answer === 'no') {
|
|
232
|
+
console.log(chalk.gray('\n No problem! Just restart Claude Code manually when ready.\n'));
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Attempt to restart Claude Code
|
|
237
|
+
console.log(chalk.gray('\n Restarting Claude Code...\n'));
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
const isWindows = process.platform === 'win32';
|
|
241
|
+
|
|
242
|
+
if (isWindows) {
|
|
243
|
+
// On Windows, spawn a new Claude process detached and exit
|
|
244
|
+
spawn('cmd', ['/c', 'start', 'claude'], {
|
|
245
|
+
cwd,
|
|
246
|
+
detached: true,
|
|
247
|
+
stdio: 'ignore',
|
|
248
|
+
shell: true,
|
|
249
|
+
}).unref();
|
|
250
|
+
} else {
|
|
251
|
+
// On Mac/Linux, spawn claude in new terminal
|
|
252
|
+
spawn('claude', [], {
|
|
253
|
+
cwd,
|
|
254
|
+
detached: true,
|
|
255
|
+
stdio: 'ignore',
|
|
256
|
+
shell: true,
|
|
257
|
+
}).unref();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
console.log(chalk.green(' ✓ Claude Code is restarting...\n'));
|
|
261
|
+
console.log(chalk.gray(' This terminal will close. Claude Code will open in a new window.\n'));
|
|
262
|
+
|
|
263
|
+
// Give the spawn a moment to start
|
|
264
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
265
|
+
|
|
266
|
+
// Exit this process
|
|
267
|
+
process.exit(0);
|
|
268
|
+
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.log(chalk.yellow(' Could not auto-restart. Please restart Claude Code manually.\n'));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Install pattern files for API key users (paid users)
|
|
276
|
+
*/
|
|
277
|
+
async function installPatternsWithApiKey(apiKey: string): Promise<void> {
|
|
278
|
+
const spinner = ora('Installing CodeBakers patterns...').start();
|
|
279
|
+
const cwd = process.cwd();
|
|
280
|
+
const apiUrl = getApiUrl();
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
const response = await fetch(`${apiUrl}/api/content`, {
|
|
284
|
+
method: 'GET',
|
|
285
|
+
headers: {
|
|
286
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
if (!response.ok) {
|
|
291
|
+
spinner.warn('Could not download patterns');
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const content: ContentResponse = await response.json();
|
|
296
|
+
await writePatternFiles(cwd, content, spinner);
|
|
297
|
+
|
|
298
|
+
} catch (error) {
|
|
299
|
+
spinner.warn('Could not install patterns');
|
|
300
|
+
console.log(chalk.gray(' Check your internet connection.\n'));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Install pattern files (CLAUDE.md and .claude/) for trial users
|
|
306
|
+
*/
|
|
307
|
+
async function installPatterns(trialId: string): Promise<void> {
|
|
308
|
+
const spinner = ora('Installing CodeBakers patterns...').start();
|
|
309
|
+
const cwd = process.cwd();
|
|
310
|
+
const apiUrl = getApiUrl();
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
// Fetch patterns using trial ID
|
|
314
|
+
const response = await fetch(`${apiUrl}/api/content`, {
|
|
315
|
+
method: 'GET',
|
|
316
|
+
headers: {
|
|
317
|
+
'X-Trial-ID': trialId,
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
if (!response.ok) {
|
|
322
|
+
// Try without auth - some patterns may be available for trial
|
|
323
|
+
const publicResponse = await fetch(`${apiUrl}/api/content/trial`, {
|
|
324
|
+
method: 'GET',
|
|
325
|
+
headers: {
|
|
326
|
+
'X-Trial-ID': trialId,
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
if (!publicResponse.ok) {
|
|
331
|
+
spinner.warn('Could not download patterns (will use MCP tools)');
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const content: ContentResponse = await publicResponse.json();
|
|
336
|
+
await writePatternFiles(cwd, content, spinner);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const content: ContentResponse = await response.json();
|
|
341
|
+
await writePatternFiles(cwd, content, spinner);
|
|
342
|
+
|
|
343
|
+
} catch (error) {
|
|
344
|
+
spinner.warn('Could not install patterns (will use MCP tools)');
|
|
345
|
+
console.log(chalk.gray(' Patterns will be available via MCP tools.\n'));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function writePatternFiles(cwd: string, content: ContentResponse, spinner: ReturnType<typeof ora>): Promise<void> {
|
|
350
|
+
// Check if patterns already exist
|
|
351
|
+
const claudeMdPath = join(cwd, 'CLAUDE.md');
|
|
352
|
+
if (existsSync(claudeMdPath)) {
|
|
353
|
+
spinner.succeed('CodeBakers patterns already installed');
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Write CLAUDE.md (router file)
|
|
358
|
+
if (content.router) {
|
|
359
|
+
writeFileSync(claudeMdPath, content.router);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Write pattern modules to .claude/
|
|
363
|
+
if (content.modules && Object.keys(content.modules).length > 0) {
|
|
364
|
+
const modulesDir = join(cwd, '.claude');
|
|
365
|
+
if (!existsSync(modulesDir)) {
|
|
366
|
+
mkdirSync(modulesDir, { recursive: true });
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
for (const [name, data] of Object.entries(content.modules)) {
|
|
370
|
+
writeFileSync(join(modulesDir, name), data);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Update .gitignore to exclude encoded patterns
|
|
375
|
+
const gitignorePath = join(cwd, '.gitignore');
|
|
376
|
+
if (existsSync(gitignorePath)) {
|
|
377
|
+
const { readFileSync } = await import('fs');
|
|
378
|
+
const gitignore = readFileSync(gitignorePath, 'utf-8');
|
|
379
|
+
if (!gitignore.includes('.claude/')) {
|
|
380
|
+
writeFileSync(gitignorePath, gitignore + '\n# CodeBakers patterns\n.claude/\n');
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
spinner.succeed(`CodeBakers patterns installed (v${content.version})`);
|
|
385
|
+
console.log(chalk.gray(` ${Object.keys(content.modules || {}).length} pattern modules ready\n`));
|
|
386
|
+
}
|