meetscribe 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/README.md +209 -0
- package/bin/meetscribe.js +5 -0
- package/package.json +48 -0
- package/src/cli/commands/config.js +88 -0
- package/src/cli/commands/list.js +76 -0
- package/src/cli/commands/login.js +191 -0
- package/src/cli/commands/protocol.js +182 -0
- package/src/cli/commands/status.js +62 -0
- package/src/cli/commands/transcribe.js +231 -0
- package/src/cli/commands/whoami.js +79 -0
- package/src/cli/index.js +114 -0
- package/src/cli/utils/config.js +89 -0
- package/src/cli/utils/output.js +100 -0
- package/src/index.js +7 -0
- package/src/sdk/client.js +211 -0
- package/types/index.d.ts +98 -0
package/README.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# MeetScribe CLI
|
|
2
|
+
|
|
3
|
+
Command-line interface for MeetScribe - meeting transcription and protocol generation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g meetscribe
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or use without installing:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx meetscribe transcribe meeting.mp3
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Authenticate
|
|
21
|
+
meetscribe login
|
|
22
|
+
|
|
23
|
+
# Transcribe a file
|
|
24
|
+
meetscribe transcribe meeting.mp3
|
|
25
|
+
|
|
26
|
+
# Get result as subtitles
|
|
27
|
+
meetscribe transcribe interview.mp4 --format srt -o subtitles.srt
|
|
28
|
+
|
|
29
|
+
# List your materials
|
|
30
|
+
meetscribe list
|
|
31
|
+
|
|
32
|
+
# Get meeting protocol
|
|
33
|
+
meetscribe protocol <material_id>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Authentication
|
|
37
|
+
|
|
38
|
+
### Browser Login (recommended)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
meetscribe login
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Opens your browser for authentication via MeetScribe website.
|
|
45
|
+
|
|
46
|
+
### API Key
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Enter key interactively
|
|
50
|
+
meetscribe login --key
|
|
51
|
+
|
|
52
|
+
# Or set directly
|
|
53
|
+
meetscribe config set-key sk_live_xxx
|
|
54
|
+
|
|
55
|
+
# Or use environment variable
|
|
56
|
+
export MEETSCRIBE_API_KEY=sk_live_xxx
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Commands
|
|
60
|
+
|
|
61
|
+
### Transcribe
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
meetscribe transcribe <file> [options]
|
|
65
|
+
|
|
66
|
+
Options:
|
|
67
|
+
-l, --language <lang> Language (ru, en, auto) [default: auto]
|
|
68
|
+
-s, --speakers <num> Number of speakers for diarization
|
|
69
|
+
-o, --output <file> Save output to file
|
|
70
|
+
-f, --format <format> Output format (json, txt, srt, vtt) [default: json]
|
|
71
|
+
--no-wait Return job ID immediately
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Examples:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Basic transcription
|
|
78
|
+
meetscribe transcribe meeting.mp3
|
|
79
|
+
|
|
80
|
+
# Russian language, 3 speakers
|
|
81
|
+
meetscribe transcribe meeting.mp4 -l ru -s 3
|
|
82
|
+
|
|
83
|
+
# Save as subtitles
|
|
84
|
+
meetscribe transcribe interview.mp4 -f srt -o subtitles.srt
|
|
85
|
+
|
|
86
|
+
# Non-blocking (for scripts)
|
|
87
|
+
JOB_ID=$(meetscribe transcribe large-file.mp4 --no-wait)
|
|
88
|
+
meetscribe status $JOB_ID
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### List Materials
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
meetscribe list [options]
|
|
95
|
+
|
|
96
|
+
Options:
|
|
97
|
+
-n, --limit <num> Number of items [default: 20]
|
|
98
|
+
--status <status> Filter by status
|
|
99
|
+
-f, --format <format> Output format (table, json)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Get Protocol
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
meetscribe protocol <material_id> [options]
|
|
106
|
+
|
|
107
|
+
Options:
|
|
108
|
+
-f, --format <format> Output format (json, markdown, txt)
|
|
109
|
+
-o, --output <file> Save to file
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Configuration
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Show config
|
|
116
|
+
meetscribe config show
|
|
117
|
+
|
|
118
|
+
# Set API key
|
|
119
|
+
meetscribe config set-key <key>
|
|
120
|
+
|
|
121
|
+
# Set API URL
|
|
122
|
+
meetscribe config set-url <url>
|
|
123
|
+
|
|
124
|
+
# Set default language
|
|
125
|
+
meetscribe config set-language <ru|en|auto>
|
|
126
|
+
|
|
127
|
+
# Reset to defaults
|
|
128
|
+
meetscribe config reset
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Account
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Show current user
|
|
135
|
+
meetscribe whoami
|
|
136
|
+
|
|
137
|
+
# Logout
|
|
138
|
+
meetscribe logout
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## SDK Usage
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
import { MeetScribeClient } from 'meetscribe';
|
|
145
|
+
|
|
146
|
+
const client = new MeetScribeClient({
|
|
147
|
+
apiKey: process.env.MEETSCRIBE_API_KEY
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Transcribe a file
|
|
151
|
+
const job = await client.transcribe('./meeting.mp3', {
|
|
152
|
+
language: 'ru',
|
|
153
|
+
speakers: 3
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Wait for result
|
|
157
|
+
const result = await client.waitForJob(job.data.job_id);
|
|
158
|
+
console.log(result.data.transcript);
|
|
159
|
+
|
|
160
|
+
// Get protocol
|
|
161
|
+
const protocol = await client.getProtocol(result.data.material_id);
|
|
162
|
+
console.log(protocol.data.action_items);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## CI/CD Example
|
|
166
|
+
|
|
167
|
+
```yaml
|
|
168
|
+
# GitHub Actions
|
|
169
|
+
jobs:
|
|
170
|
+
transcribe:
|
|
171
|
+
runs-on: ubuntu-latest
|
|
172
|
+
steps:
|
|
173
|
+
- uses: actions/checkout@v4
|
|
174
|
+
|
|
175
|
+
- name: Install MeetScribe
|
|
176
|
+
run: npm install -g meetscribe
|
|
177
|
+
|
|
178
|
+
- name: Transcribe
|
|
179
|
+
env:
|
|
180
|
+
MEETSCRIBE_API_KEY: ${{ secrets.MEETSCRIBE_API_KEY }}
|
|
181
|
+
run: |
|
|
182
|
+
meetscribe transcribe recording.mp3 -f txt -o transcript.txt
|
|
183
|
+
|
|
184
|
+
- uses: actions/upload-artifact@v4
|
|
185
|
+
with:
|
|
186
|
+
name: transcript
|
|
187
|
+
path: transcript.txt
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Exit Codes
|
|
191
|
+
|
|
192
|
+
| Code | Meaning |
|
|
193
|
+
|------|---------|
|
|
194
|
+
| 0 | Success |
|
|
195
|
+
| 1 | General error |
|
|
196
|
+
| 2 | Invalid arguments |
|
|
197
|
+
| 3 | Authentication error |
|
|
198
|
+
| 4 | API error |
|
|
199
|
+
| 5 | File not found |
|
|
200
|
+
|
|
201
|
+
## Links
|
|
202
|
+
|
|
203
|
+
- [MeetScribe](https://meetscribe.ru)
|
|
204
|
+
- [API Documentation](https://meetscribe.ru/api-docs)
|
|
205
|
+
- [Support](https://t.me/meetscribe_bot)
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "meetscribe",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MeetScribe CLI - Meeting transcription and protocol generation",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"meetscribe",
|
|
7
|
+
"transcription",
|
|
8
|
+
"meeting",
|
|
9
|
+
"protocol",
|
|
10
|
+
"speech-to-text",
|
|
11
|
+
"cli",
|
|
12
|
+
"whisper"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://meetscribe.ru",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/meetscribe/meetscribe-cli"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "MeetScribe Team",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "src/index.js",
|
|
23
|
+
"types": "types/index.d.ts",
|
|
24
|
+
"bin": {
|
|
25
|
+
"meetscribe": "./bin/meetscribe.js"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18.0.0"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"test": "node --test",
|
|
32
|
+
"lint": "eslint src/",
|
|
33
|
+
"start": "node bin/meetscribe.js"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"axios": "^1.7.0",
|
|
37
|
+
"chalk": "^5.3.0",
|
|
38
|
+
"commander": "^12.1.0",
|
|
39
|
+
"conf": "^13.0.0",
|
|
40
|
+
"form-data": "^4.0.0",
|
|
41
|
+
"mime-types": "^2.1.35",
|
|
42
|
+
"open": "^10.1.0",
|
|
43
|
+
"ora": "^8.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"eslint": "^9.0.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config command - manage CLI configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import config, { getAllConfig, setApiKey, clearConfig } from '../utils/config.js';
|
|
7
|
+
import { success, error, info, table } from '../utils/output.js';
|
|
8
|
+
|
|
9
|
+
export function configCommand(action, value) {
|
|
10
|
+
switch (action) {
|
|
11
|
+
case 'show': {
|
|
12
|
+
console.log();
|
|
13
|
+
console.log(chalk.bold('MeetScribe CLI Configuration'));
|
|
14
|
+
console.log();
|
|
15
|
+
|
|
16
|
+
const cfg = getAllConfig();
|
|
17
|
+
|
|
18
|
+
table(
|
|
19
|
+
['Setting', 'Value'],
|
|
20
|
+
[
|
|
21
|
+
['API Key', cfg.apiKey],
|
|
22
|
+
['API URL', cfg.apiUrl],
|
|
23
|
+
['Default Language', cfg.defaultLanguage],
|
|
24
|
+
['Default Format', cfg.defaultFormat],
|
|
25
|
+
['Config Path', cfg.configPath]
|
|
26
|
+
]
|
|
27
|
+
);
|
|
28
|
+
console.log();
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
case 'set-key': {
|
|
33
|
+
if (!value) {
|
|
34
|
+
error('API key is required');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!value.startsWith('sk_live_') && !value.startsWith('sk_test_')) {
|
|
39
|
+
error('Invalid API key format. Key should start with sk_live_ or sk_test_');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setApiKey(value);
|
|
44
|
+
success('API key saved');
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
case 'set-url': {
|
|
49
|
+
if (!value) {
|
|
50
|
+
error('URL is required');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
new URL(value);
|
|
56
|
+
} catch {
|
|
57
|
+
error('Invalid URL format');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
config.set('apiUrl', value);
|
|
62
|
+
success(`API URL set to ${value}`);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
case 'set-language': {
|
|
67
|
+
const allowed = ['ru', 'en', 'auto'];
|
|
68
|
+
if (!allowed.includes(value)) {
|
|
69
|
+
error(`Invalid language. Allowed: ${allowed.join(', ')}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
config.set('defaultLanguage', value);
|
|
74
|
+
success(`Default language set to ${value}`);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
case 'reset': {
|
|
79
|
+
clearConfig();
|
|
80
|
+
success('Configuration reset to defaults');
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
default:
|
|
85
|
+
error(`Unknown config action: ${action}`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List command - list transcribed materials
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
|
|
8
|
+
import { getApiKey, getApiUrl } from '../utils/config.js';
|
|
9
|
+
import { MeetScribeClient } from '../../sdk/client.js';
|
|
10
|
+
import { error, json, table, statusBadge, formatDate } from '../utils/output.js';
|
|
11
|
+
|
|
12
|
+
export async function listCommand(options, command) {
|
|
13
|
+
// Check auth
|
|
14
|
+
const apiKey = command?.parent?.opts()?.apiKey || getApiKey();
|
|
15
|
+
if (!apiKey) {
|
|
16
|
+
error('Not authenticated. Run `meetscribe login` first.');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const client = new MeetScribeClient({
|
|
21
|
+
apiKey,
|
|
22
|
+
baseUrl: command?.parent?.opts()?.apiUrl || getApiUrl()
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const spinner = ora('Fetching materials...').start();
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const result = await client.listMaterials({
|
|
29
|
+
perPage: parseInt(options.limit) || 20,
|
|
30
|
+
status: options.status
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
spinner.stop();
|
|
34
|
+
|
|
35
|
+
const materials = result.data?.materials || [];
|
|
36
|
+
const pagination = result.data?.pagination || {};
|
|
37
|
+
|
|
38
|
+
if (options.format === 'json') {
|
|
39
|
+
json(result.data);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log();
|
|
44
|
+
|
|
45
|
+
if (materials.length === 0) {
|
|
46
|
+
console.log(chalk.dim('No materials found'));
|
|
47
|
+
console.log();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(chalk.bold(`Materials (${pagination.total || materials.length} total)`));
|
|
52
|
+
console.log();
|
|
53
|
+
|
|
54
|
+
table(
|
|
55
|
+
['ID', 'Title', 'Status', 'Created'],
|
|
56
|
+
materials.map(m => [
|
|
57
|
+
m.id?.slice(0, 12) + '...' || '-',
|
|
58
|
+
(m.title || 'Untitled').slice(0, 40),
|
|
59
|
+
m.status,
|
|
60
|
+
m.created_at ? formatDate(m.created_at) : '-'
|
|
61
|
+
])
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
console.log();
|
|
65
|
+
|
|
66
|
+
if (pagination.total > materials.length) {
|
|
67
|
+
console.log(chalk.dim(`Showing ${materials.length} of ${pagination.total}. Use --limit to show more.`));
|
|
68
|
+
console.log();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
} catch (err) {
|
|
72
|
+
spinner.fail('Failed to fetch materials');
|
|
73
|
+
error(err.message);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login command - authenticate with MeetScribe
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import open from 'open';
|
|
8
|
+
import readline from 'readline';
|
|
9
|
+
|
|
10
|
+
import config, { setApiKey, getApiKey } from '../utils/config.js';
|
|
11
|
+
import { MeetScribeClient } from '../../sdk/client.js';
|
|
12
|
+
import { success, error, info } from '../utils/output.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Interactive prompt for input
|
|
16
|
+
*/
|
|
17
|
+
async function prompt(question) {
|
|
18
|
+
const rl = readline.createInterface({
|
|
19
|
+
input: process.stdin,
|
|
20
|
+
output: process.stdout
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
rl.question(question, (answer) => {
|
|
25
|
+
rl.close();
|
|
26
|
+
resolve(answer.trim());
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Login via API key input
|
|
33
|
+
*/
|
|
34
|
+
async function loginWithKey() {
|
|
35
|
+
console.log();
|
|
36
|
+
info('Enter your API key from https://meetscribe.ru/api-settings');
|
|
37
|
+
console.log();
|
|
38
|
+
|
|
39
|
+
const key = await prompt('API Key: ');
|
|
40
|
+
|
|
41
|
+
if (!key) {
|
|
42
|
+
error('No API key provided');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Validate key format
|
|
47
|
+
if (!key.startsWith('sk_live_') && !key.startsWith('sk_test_')) {
|
|
48
|
+
error('Invalid API key format. Key should start with sk_live_ or sk_test_');
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Test the key
|
|
53
|
+
const spinner = ora('Validating API key...').start();
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const client = new MeetScribeClient({ apiKey: key });
|
|
57
|
+
await client.getAccount();
|
|
58
|
+
|
|
59
|
+
setApiKey(key);
|
|
60
|
+
spinner.succeed('API key validated and saved');
|
|
61
|
+
|
|
62
|
+
console.log();
|
|
63
|
+
success(`Logged in successfully!`);
|
|
64
|
+
console.log(chalk.dim(`Config saved to: ${config.path}`));
|
|
65
|
+
} catch (err) {
|
|
66
|
+
spinner.fail('Invalid API key');
|
|
67
|
+
error(err.message);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Login via browser OAuth
|
|
74
|
+
*/
|
|
75
|
+
async function loginWithBrowser() {
|
|
76
|
+
const spinner = ora('Creating auth session...').start();
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
// Create a temporary client without API key
|
|
80
|
+
const client = new MeetScribeClient({
|
|
81
|
+
baseUrl: config.get('apiUrl')
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Get session
|
|
85
|
+
const session = await client.createCliSession();
|
|
86
|
+
const authUrl = session.data?.auth_url;
|
|
87
|
+
const sessionId = session.data?.session_id;
|
|
88
|
+
|
|
89
|
+
if (!authUrl || !sessionId) {
|
|
90
|
+
spinner.fail('Failed to create auth session');
|
|
91
|
+
error('Server returned invalid response');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
spinner.stop();
|
|
96
|
+
|
|
97
|
+
console.log();
|
|
98
|
+
info('Opening browser for authentication...');
|
|
99
|
+
console.log();
|
|
100
|
+
console.log(chalk.dim('If browser does not open, visit:'));
|
|
101
|
+
console.log(chalk.cyan(authUrl));
|
|
102
|
+
console.log();
|
|
103
|
+
|
|
104
|
+
// Open browser
|
|
105
|
+
await open(authUrl);
|
|
106
|
+
|
|
107
|
+
// Poll for token
|
|
108
|
+
const pollSpinner = ora('Waiting for authorization...').start();
|
|
109
|
+
|
|
110
|
+
const startTime = Date.now();
|
|
111
|
+
const timeout = 5 * 60 * 1000; // 5 minutes
|
|
112
|
+
|
|
113
|
+
while (true) {
|
|
114
|
+
try {
|
|
115
|
+
const result = await client.pollCliToken(sessionId);
|
|
116
|
+
|
|
117
|
+
if (result.data?.status === 'authorized' && result.data?.api_key) {
|
|
118
|
+
pollSpinner.succeed('Authorization successful');
|
|
119
|
+
|
|
120
|
+
setApiKey(result.data.api_key);
|
|
121
|
+
|
|
122
|
+
console.log();
|
|
123
|
+
success(`Logged in as ${result.data.user?.username || result.data.user?.name || 'user'}`);
|
|
124
|
+
console.log(chalk.dim(`Config saved to: ${config.path}`));
|
|
125
|
+
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (result.data?.status === 'expired') {
|
|
130
|
+
pollSpinner.fail('Session expired');
|
|
131
|
+
error('Authorization session has expired. Please try again.');
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
} catch (err) {
|
|
135
|
+
// Ignore errors during polling, keep trying
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (Date.now() - startTime > timeout) {
|
|
139
|
+
pollSpinner.fail('Timeout');
|
|
140
|
+
error('Authorization timed out. Please try again.');
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Wait before next poll
|
|
145
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
146
|
+
}
|
|
147
|
+
} catch (err) {
|
|
148
|
+
spinner.fail('Failed to start auth');
|
|
149
|
+
error(err.message);
|
|
150
|
+
|
|
151
|
+
// Fallback to key input
|
|
152
|
+
console.log();
|
|
153
|
+
info('Falling back to API key input...');
|
|
154
|
+
await loginWithKey();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Main login command handler
|
|
160
|
+
*/
|
|
161
|
+
export async function loginCommand(options) {
|
|
162
|
+
// Check if already logged in
|
|
163
|
+
const existingKey = getApiKey();
|
|
164
|
+
if (existingKey) {
|
|
165
|
+
console.log();
|
|
166
|
+
info('You are already logged in');
|
|
167
|
+
console.log(chalk.dim(`API Key: ${existingKey.slice(0, 8)}...${existingKey.slice(-4)}`));
|
|
168
|
+
console.log();
|
|
169
|
+
|
|
170
|
+
const answer = await prompt('Do you want to re-login? (y/N): ');
|
|
171
|
+
if (answer.toLowerCase() !== 'y') {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.log();
|
|
177
|
+
console.log(chalk.bold('MeetScribe CLI Login'));
|
|
178
|
+
console.log();
|
|
179
|
+
|
|
180
|
+
if (options.key) {
|
|
181
|
+
await loginWithKey();
|
|
182
|
+
} else if (options.device) {
|
|
183
|
+
// Device flow - show code for manual entry
|
|
184
|
+
console.log();
|
|
185
|
+
info('Device flow not yet implemented. Using API key input...');
|
|
186
|
+
await loginWithKey();
|
|
187
|
+
} else {
|
|
188
|
+
// Default: browser OAuth
|
|
189
|
+
await loginWithBrowser();
|
|
190
|
+
}
|
|
191
|
+
}
|