vibe-weaver 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.
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Auth module for Vibe-Weaver CLI
3
+ * Handles API key and GitHub token storage via keytar with plaintext fallback
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+ import inquirer from 'inquirer';
8
+ import open from 'open';
9
+ import { loadConfig, saveConfig, getSupabaseUrl, getConfigPath } from '../config/index.js';
10
+
11
+ const SERVICE_NAME = 'vibe-weaver';
12
+ const API_KEY_ACCOUNT = 'api-key';
13
+ const GITHUB_TOKEN_ACCOUNT = 'github-token';
14
+
15
+ // Try to use keytar for secure storage
16
+ let keytar: any = null;
17
+ try {
18
+ keytar = require('keytar');
19
+ } catch {
20
+ // keytar not available, will use plaintext fallback
21
+ }
22
+
23
+ function getPlaintextPath(): string {
24
+ return getConfigPath();
25
+ }
26
+
27
+ /**
28
+ * Try to get a secret from keytar, fall back to config file
29
+ */
30
+ async function getSecret(account: string): Promise<string | null> {
31
+ if (keytar) {
32
+ try {
33
+ const value = await keytar.getPassword(SERVICE_NAME, account);
34
+ if (value) return value;
35
+ } catch {
36
+ // Keytar failed, fall through to plaintext
37
+ }
38
+ }
39
+
40
+ // Fallback to config file
41
+ const config = loadConfig();
42
+ if (account === API_KEY_ACCOUNT) return config.apiKey || null;
43
+ if (account === GITHUB_TOKEN_ACCOUNT) return config.githubToken || null;
44
+ return null;
45
+ }
46
+
47
+ /**
48
+ * Store a secret in keytar, fall back to config file
49
+ */
50
+ async function setSecret(account: string, value: string): Promise<void> {
51
+ if (keytar) {
52
+ try {
53
+ await keytar.setPassword(SERVICE_NAME, account, value);
54
+ // Also save to config as backup
55
+ const config = loadConfig();
56
+ if (account === API_KEY_ACCOUNT) config.apiKey = value;
57
+ if (account === GITHUB_TOKEN_ACCOUNT) config.githubToken = value;
58
+ saveConfig(config);
59
+ return;
60
+ } catch {
61
+ // Keytar failed, fall through to plaintext
62
+ }
63
+ }
64
+
65
+ // Fallback to config file
66
+ const config = loadConfig();
67
+ if (account === API_KEY_ACCOUNT) config.apiKey = value;
68
+ if (account === GITHUB_TOKEN_ACCOUNT) config.githubToken = value;
69
+ saveConfig(config);
70
+ }
71
+
72
+ /**
73
+ * Remove a secret from keytar and config
74
+ */
75
+ async function deleteSecret(account: string): Promise<void> {
76
+ if (keytar) {
77
+ try {
78
+ await keytar.deletePassword(SERVICE_NAME, account);
79
+ } catch {
80
+ // Ignore errors
81
+ }
82
+ }
83
+
84
+ const config = loadConfig();
85
+ if (account === API_KEY_ACCOUNT) {
86
+ config.apiKey = undefined;
87
+ }
88
+ if (account === GITHUB_TOKEN_ACCOUNT) {
89
+ config.githubToken = undefined;
90
+ }
91
+ saveConfig(config);
92
+ }
93
+
94
+ /**
95
+ * Check if user is logged in (has API key)
96
+ */
97
+ export async function isLoggedIn(): Promise<boolean> {
98
+ const apiKey = await getSecret(API_KEY_ACCOUNT);
99
+ return !!apiKey;
100
+ }
101
+
102
+ /**
103
+ * Get current API key
104
+ */
105
+ export async function getApiKey(): Promise<string | null> {
106
+ return getSecret(API_KEY_ACCOUNT);
107
+ }
108
+
109
+ /**
110
+ * Get current GitHub token
111
+ */
112
+ export async function getGithubToken(): Promise<string | null> {
113
+ return getSecret(GITHUB_TOKEN_ACCOUNT);
114
+ }
115
+
116
+ /**
117
+ * Interactive login - accept API key or open web page
118
+ */
119
+ export async function login(options: { apiKey?: string; openBrowser?: boolean } = {}): Promise<void> {
120
+ const supabaseUrl = getSupabaseUrl();
121
+
122
+ if (options.apiKey) {
123
+ // Direct API key provided
124
+ await setSecret(API_KEY_ACCOUNT, options.apiKey);
125
+ console.log(chalk.green('✓ API key saved securely'));
126
+
127
+ // Verify the key works
128
+ const userInfo = await getUserInfo();
129
+ if (userInfo) {
130
+ console.log(chalk.green(`✓ Logged in as ${userInfo.email} (${userInfo.plan} plan)`));
131
+ }
132
+ return;
133
+ }
134
+
135
+ if (options.openBrowser) {
136
+ // Open the web page for API key creation
137
+ const apiPageUrl = `${supabaseUrl}/dashboard/api`;
138
+ console.log(chalk.blue('Opening browser to create API key...'));
139
+ await open(apiPageUrl);
140
+ }
141
+
142
+ // Interactive prompt for API key
143
+ const answers = await inquirer.prompt([
144
+ {
145
+ type: 'input',
146
+ name: 'apiKey',
147
+ message: 'Enter your Vibe-Weaver API key (vw_...):',
148
+ validate: (input: string) => {
149
+ if (!input.startsWith('vw_') && input.length < 10) {
150
+ return 'Please enter a valid API key (starts with vw_)';
151
+ }
152
+ return true;
153
+ }
154
+ }
155
+ ]);
156
+
157
+ await setSecret(API_KEY_ACCOUNT, answers.apiKey);
158
+ console.log(chalk.green('✓ API key saved securely'));
159
+
160
+ // Verify the key works
161
+ const userInfo = await getUserInfo();
162
+ if (userInfo) {
163
+ console.log(chalk.green(`✓ Logged in as ${userInfo.email} (${userInfo.plan} plan)`));
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Login with GitHub token
169
+ */
170
+ export async function loginGithub(token?: string): Promise<void> {
171
+ if (!token) {
172
+ const answers = await inquirer.prompt([
173
+ {
174
+ type: 'password',
175
+ name: 'token',
176
+ message: 'Enter your GitHub Personal Access Token:',
177
+ validate: (input: string) => {
178
+ if (input.length < 10) {
179
+ return 'Please enter a valid GitHub token';
180
+ }
181
+ return true;
182
+ }
183
+ }
184
+ ]);
185
+ token = answers.token;
186
+ }
187
+
188
+ await setSecret(GITHUB_TOKEN_ACCOUNT, token);
189
+ console.log(chalk.green('✓ GitHub token saved securely'));
190
+ }
191
+
192
+ /**
193
+ * Logout - clear all stored credentials
194
+ */
195
+ export async function logout(): Promise<void> {
196
+ await deleteSecret(API_KEY_ACCOUNT);
197
+ await deleteSecret(GITHUB_TOKEN_ACCOUNT);
198
+ console.log(chalk.green('✓ Logged out successfully'));
199
+ }
200
+
201
+ /**
202
+ * Get user info from the API
203
+ */
204
+ export async function getUserInfo(): Promise<{ email: string; plan: string; credits: number } | null> {
205
+ const apiKey = await getApiKey();
206
+ if (!apiKey) return null;
207
+
208
+ const supabaseUrl = getSupabaseUrl();
209
+
210
+ try {
211
+ const response = await fetch(`${supabaseUrl}/rest/v1/profiles?select=email,plan,credits`, {
212
+ headers: {
213
+ 'Authorization': `Bearer ${apiKey}`,
214
+ 'apikey': `${supabaseUrl}/rest/v1/apikey`
215
+ }
216
+ });
217
+
218
+ if (response.ok) {
219
+ const data = await response.json();
220
+ if (Array.isArray(data) && data.length > 0) {
221
+ return {
222
+ email: data[0].email || 'Unknown',
223
+ plan: data[0].plan || 'free',
224
+ credits: data[0].credits || 0
225
+ };
226
+ }
227
+ }
228
+ } catch (error) {
229
+ console.warn('Warning: Could not fetch user info:', error);
230
+ }
231
+
232
+ return null;
233
+ }
234
+
235
+ /**
236
+ * Show current user info (whoami)
237
+ */
238
+ export async function whoami(): Promise<void> {
239
+ const apiKey = await getApiKey();
240
+ if (!apiKey) {
241
+ console.log(chalk.yellow('Not logged in. Run "vibe-weaver login" first.'));
242
+ return;
243
+ }
244
+
245
+ const userInfo = await getUserInfo();
246
+ if (userInfo) {
247
+ console.log(chalk.bold('Logged in as:'));
248
+ console.log(` Email: ${userInfo.email}`);
249
+ console.log(` Plan: ${chalk.cyan(userInfo.plan)}`);
250
+ console.log(` Credits: ${chalk.green(userInfo.credits.toString())}`);
251
+ } else {
252
+ console.log(chalk.yellow('API key is set but could not fetch user info.'));
253
+ console.log(` API Key: ${chalk.grey(apiKey.substring(0, 10))}...`);
254
+ }
255
+
256
+ const githubToken = await getGithubToken();
257
+ if (githubToken) {
258
+ console.log(chalk.green('✓ GitHub token configured'));
259
+ } else {
260
+ console.log(chalk.grey(' GitHub: Not configured (run "vibe-weaver login --github" to add)'));
261
+ }
262
+ }
263
+
264
+ export default {
265
+ login,
266
+ logout,
267
+ whoami,
268
+ loginGithub,
269
+ isLoggedIn,
270
+ getApiKey,
271
+ getGithubToken,
272
+ getUserInfo
273
+ };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Configuration module for Vibe-Weaver CLI
3
+ * Manages config storage in ~/.vibe-weaver/config.json
4
+ */
5
+
6
+ import envPaths from 'env-paths';
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import os from 'os';
10
+
11
+ export interface CLIConfig {
12
+ apiKey?: string;
13
+ githubToken?: string;
14
+ supabaseUrl?: string;
15
+ defaultPlatform?: string;
16
+ defaultMode?: string;
17
+ autoApprove?: boolean;
18
+ autoDeploy?: boolean;
19
+ }
20
+
21
+ export interface UserInfo {
22
+ email?: string;
23
+ plan?: string;
24
+ credits?: number;
25
+ }
26
+
27
+ const CONFIG_FILE_NAME = 'config.json';
28
+
29
+ function getConfigDir(): string {
30
+ const paths = envPaths('vibe-weaver', { suffix: '' });
31
+ if (!fs.existsSync(paths.config)) {
32
+ fs.mkdirSync(paths.config, { recursive: true });
33
+ }
34
+ // Set restrictive permissions
35
+ fs.chmodSync(paths.config, 0o700);
36
+ return paths.config;
37
+ }
38
+
39
+ export function getConfigPath(): string {
40
+ return path.join(getConfigDir(), CONFIG_FILE_NAME);
41
+ }
42
+
43
+ export function loadConfig(): CLIConfig {
44
+ const configPath = getConfigPath();
45
+ try {
46
+ if (fs.existsSync(configPath)) {
47
+ const data = fs.readFileSync(configPath, 'utf-8');
48
+ return JSON.parse(data);
49
+ }
50
+ } catch (error) {
51
+ console.warn('Warning: Failed to load config:', error);
52
+ }
53
+ return {};
54
+ }
55
+
56
+ export function saveConfig(config: CLIConfig): void {
57
+ const configPath = getConfigPath();
58
+ try {
59
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), {
60
+ mode: 0o600
61
+ });
62
+ } catch (error) {
63
+ throw new Error(`Failed to save config: ${error}`);
64
+ }
65
+ }
66
+
67
+ export function clearConfig(): void {
68
+ const configPath = getConfigPath();
69
+ try {
70
+ if (fs.existsSync(configPath)) {
71
+ fs.unlinkSync(configPath);
72
+ }
73
+ } catch (error) {
74
+ throw new Error(`Failed to clear config: ${error}`);
75
+ }
76
+ }
77
+
78
+ export function getDefaultSupabaseUrl(): string {
79
+ // Default to vibe-weaver's Supabase instance
80
+ return process.env.VW_SUPABASE_URL || 'https://mmtsoqelnsjleusqyknc.supabase.co';
81
+ }
82
+
83
+ export function getSupabaseUrl(): string {
84
+ const config = loadConfig();
85
+ return config.supabaseUrl || getDefaultSupabaseUrl();
86
+ }