@promptcellar/pc 0.2.0 → 0.3.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/bin/pc.js CHANGED
@@ -13,11 +13,12 @@ import { update } from '../src/commands/update.js';
13
13
  program
14
14
  .name('pc')
15
15
  .description('PromptCellar CLI - sync prompts between your terminal and the cloud')
16
- .version('0.1.0');
16
+ .version('0.3.1');
17
17
 
18
18
  program
19
19
  .command('login')
20
- .description('Authenticate with PromptCellar')
20
+ .description('Authenticate with PromptCellar via browser')
21
+ .option('-u, --url <url>', 'API URL (for self-hosted instances)')
21
22
  .action(login);
22
23
 
23
24
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptcellar/pc",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "CLI for PromptCellar - sync prompts between your terminal and the cloud",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -1,56 +1,121 @@
1
- import inquirer from 'inquirer';
2
1
  import ora from 'ora';
3
2
  import chalk from 'chalk';
4
3
  import { setApiKey, setApiUrl, isLoggedIn } from '../lib/config.js';
5
- import { testConnection } from '../lib/api.js';
6
4
 
7
- export async function login() {
8
- if (isLoggedIn()) {
9
- const { overwrite } = await inquirer.prompt([{
10
- type: 'confirm',
11
- name: 'overwrite',
12
- message: 'Already logged in. Replace existing credentials?',
13
- default: false
14
- }]);
15
-
16
- if (!overwrite) {
17
- console.log('Login cancelled.');
18
- return;
19
- }
5
+ const DEFAULT_API_URL = 'https://prompts.weldedanvil.com';
6
+
7
+ async function initiateDeviceAuth(apiUrl) {
8
+ const response = await fetch(`${apiUrl}/auth/device`, {
9
+ method: 'POST',
10
+ headers: { 'Content-Type': 'application/json' },
11
+ body: JSON.stringify({ device_name: getDeviceName() })
12
+ });
13
+
14
+ if (!response.ok) {
15
+ throw new Error('Failed to initiate device authentication');
20
16
  }
21
17
 
22
- const answers = await inquirer.prompt([
23
- {
24
- type: 'input',
25
- name: 'apiKey',
26
- message: 'Enter your PromptCellar API key:',
27
- validate: (input) => {
28
- if (!input.trim()) return 'API key is required';
29
- if (!input.startsWith('pk_')) return 'Invalid API key format (should start with pk_)';
30
- return true;
31
- }
32
- },
33
- {
34
- type: 'input',
35
- name: 'apiUrl',
36
- message: 'API URL (press enter for default):',
37
- default: 'https://prompts.weldedanvil.com'
18
+ return response.json();
19
+ }
20
+
21
+ async function pollForApproval(apiUrl, deviceCode, interval, expiresIn) {
22
+ const startTime = Date.now();
23
+ const expiresAt = startTime + (expiresIn * 1000);
24
+
25
+ while (Date.now() < expiresAt) {
26
+ await sleep(interval * 1000);
27
+
28
+ const response = await fetch(`${apiUrl}/auth/device/poll`, {
29
+ method: 'POST',
30
+ headers: { 'Content-Type': 'application/json' },
31
+ body: JSON.stringify({ device_code: deviceCode })
32
+ });
33
+
34
+ const data = await response.json();
35
+
36
+ if (response.ok && data.access_token) {
37
+ return data;
38
+ }
39
+
40
+ if (data.error === 'authorization_pending') {
41
+ continue;
42
+ }
43
+
44
+ if (data.error === 'expired_token') {
45
+ throw new Error('Authorization request expired. Please try again.');
38
46
  }
39
- ]);
40
47
 
41
- const spinner = ora('Testing connection...').start();
48
+ if (data.error === 'access_denied') {
49
+ throw new Error('Authorization denied.');
50
+ }
51
+ }
52
+
53
+ throw new Error('Authorization request timed out. Please try again.');
54
+ }
42
55
 
43
- setApiKey(answers.apiKey.trim());
44
- setApiUrl(answers.apiUrl.trim());
56
+ function getDeviceName() {
57
+ const os = process.platform;
58
+ const hostname = process.env.HOSTNAME || process.env.COMPUTERNAME || 'CLI';
45
59
 
46
- const result = await testConnection();
60
+ const osNames = {
61
+ darwin: 'macOS',
62
+ linux: 'Linux',
63
+ win32: 'Windows'
64
+ };
65
+
66
+ return `${osNames[os] || os} - ${hostname}`;
67
+ }
68
+
69
+ function sleep(ms) {
70
+ return new Promise(resolve => setTimeout(resolve, ms));
71
+ }
72
+
73
+ export async function login(options) {
74
+ const apiUrl = options?.url || DEFAULT_API_URL;
75
+
76
+ if (isLoggedIn()) {
77
+ console.log(chalk.yellow('Already logged in.'));
78
+ console.log('Run ' + chalk.cyan('pc logout') + ' first to switch accounts.\n');
79
+ return;
80
+ }
81
+
82
+ console.log(chalk.bold('\nPromptCellar CLI Login\n'));
83
+
84
+ const spinner = ora('Connecting...').start();
85
+
86
+ try {
87
+ // Step 1: Request device code
88
+ const authData = await initiateDeviceAuth(apiUrl);
89
+ spinner.stop();
90
+
91
+ // Step 2: Show login URL and code
92
+ const verifyUrl = `${authData.verification_url}?code=${authData.user_code}`;
93
+
94
+ console.log('Open this URL in your browser to log in:\n');
95
+ console.log(chalk.cyan.bold(` ${verifyUrl}\n`));
96
+ console.log(chalk.dim(`Or go to ${authData.verification_url} and enter code: ${authData.user_code}\n`));
97
+
98
+ // Step 3: Poll for approval
99
+ spinner.start('Waiting for you to authorize in browser...');
100
+
101
+ const result = await pollForApproval(
102
+ apiUrl,
103
+ authData.device_code,
104
+ authData.interval,
105
+ authData.expires_in
106
+ );
107
+
108
+ // Step 4: Save credentials
109
+ setApiKey(result.access_token);
110
+ setApiUrl(apiUrl);
47
111
 
48
- if (result.success) {
49
112
  spinner.succeed(chalk.green('Logged in successfully!'));
50
- console.log('\nRun ' + chalk.cyan('pc setup') + ' to configure auto-capture for your CLI tools.');
51
- } else {
52
- spinner.fail(chalk.red('Connection failed: ' + result.error));
53
- console.log('Check your API key and try again.');
113
+ console.log(`\nWelcome, ${chalk.cyan(result.user_email)}!`);
114
+ console.log('\nRun ' + chalk.cyan('pc setup') + ' to configure auto-capture for your CLI tools.\n');
115
+
116
+ } catch (error) {
117
+ spinner.fail(chalk.red('Login failed: ' + error.message));
118
+ console.log('\nPlease try again or visit ' + chalk.cyan(apiUrl) + ' to sign up.\n');
54
119
  }
55
120
  }
56
121