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
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for MeetScribe CLI
|
|
3
|
+
* Stores config in ~/.meetscribe/config.json
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import Conf from 'conf';
|
|
7
|
+
|
|
8
|
+
const config = new Conf({
|
|
9
|
+
projectName: 'meetscribe',
|
|
10
|
+
schema: {
|
|
11
|
+
apiKey: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
default: ''
|
|
14
|
+
},
|
|
15
|
+
apiUrl: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
default: 'https://api.meetscribe.ru/v1'
|
|
18
|
+
},
|
|
19
|
+
defaultLanguage: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
enum: ['ru', 'en', 'auto'],
|
|
22
|
+
default: 'auto'
|
|
23
|
+
},
|
|
24
|
+
defaultFormat: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
enum: ['json', 'txt', 'srt', 'vtt', 'markdown'],
|
|
27
|
+
default: 'json'
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export default config;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get API key from environment or config
|
|
36
|
+
*/
|
|
37
|
+
export function getApiKey() {
|
|
38
|
+
// Priority: env > config
|
|
39
|
+
return process.env.MEETSCRIBE_API_KEY || config.get('apiKey');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get API URL from environment or config
|
|
44
|
+
*/
|
|
45
|
+
export function getApiUrl() {
|
|
46
|
+
return process.env.MEETSCRIBE_API_URL || config.get('apiUrl');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if user is authenticated
|
|
51
|
+
*/
|
|
52
|
+
export function isAuthenticated() {
|
|
53
|
+
return Boolean(getApiKey());
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Set API key
|
|
58
|
+
*/
|
|
59
|
+
export function setApiKey(key) {
|
|
60
|
+
config.set('apiKey', key);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Clear all config
|
|
65
|
+
*/
|
|
66
|
+
export function clearConfig() {
|
|
67
|
+
config.clear();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get all config values
|
|
72
|
+
*/
|
|
73
|
+
export function getAllConfig() {
|
|
74
|
+
return {
|
|
75
|
+
apiKey: config.get('apiKey') ? maskApiKey(config.get('apiKey')) : '(not set)',
|
|
76
|
+
apiUrl: config.get('apiUrl'),
|
|
77
|
+
defaultLanguage: config.get('defaultLanguage'),
|
|
78
|
+
defaultFormat: config.get('defaultFormat'),
|
|
79
|
+
configPath: config.path
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Mask API key for display
|
|
85
|
+
*/
|
|
86
|
+
function maskApiKey(key) {
|
|
87
|
+
if (!key || key.length < 12) return '****';
|
|
88
|
+
return key.slice(0, 8) + '...' + key.slice(-4);
|
|
89
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output formatting utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Print success message
|
|
9
|
+
*/
|
|
10
|
+
export function success(message) {
|
|
11
|
+
console.log(chalk.green('✓'), message);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Print error message
|
|
16
|
+
*/
|
|
17
|
+
export function error(message) {
|
|
18
|
+
console.error(chalk.red('✗'), message);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Print warning message
|
|
23
|
+
*/
|
|
24
|
+
export function warn(message) {
|
|
25
|
+
console.log(chalk.yellow('⚠'), message);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Print info message
|
|
30
|
+
*/
|
|
31
|
+
export function info(message) {
|
|
32
|
+
console.log(chalk.blue('ℹ'), message);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Print JSON output
|
|
37
|
+
*/
|
|
38
|
+
export function json(data) {
|
|
39
|
+
console.log(JSON.stringify(data, null, 2));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Print table
|
|
44
|
+
*/
|
|
45
|
+
export function table(headers, rows) {
|
|
46
|
+
// Calculate column widths
|
|
47
|
+
const widths = headers.map((h, i) => {
|
|
48
|
+
const maxRow = Math.max(...rows.map(r => String(r[i] || '').length));
|
|
49
|
+
return Math.max(h.length, maxRow);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Print header
|
|
53
|
+
const headerLine = headers.map((h, i) => h.padEnd(widths[i])).join(' ');
|
|
54
|
+
console.log(chalk.bold(headerLine));
|
|
55
|
+
console.log('-'.repeat(headerLine.length));
|
|
56
|
+
|
|
57
|
+
// Print rows
|
|
58
|
+
rows.forEach(row => {
|
|
59
|
+
const line = row.map((cell, i) => String(cell || '').padEnd(widths[i])).join(' ');
|
|
60
|
+
console.log(line);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Format duration in seconds to HH:MM:SS
|
|
66
|
+
*/
|
|
67
|
+
export function formatDuration(seconds) {
|
|
68
|
+
const h = Math.floor(seconds / 3600);
|
|
69
|
+
const m = Math.floor((seconds % 3600) / 60);
|
|
70
|
+
const s = Math.floor(seconds % 60);
|
|
71
|
+
|
|
72
|
+
if (h > 0) {
|
|
73
|
+
return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
|
74
|
+
}
|
|
75
|
+
return `${m}:${s.toString().padStart(2, '0')}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Format date
|
|
80
|
+
*/
|
|
81
|
+
export function formatDate(dateString) {
|
|
82
|
+
const date = new Date(dateString);
|
|
83
|
+
return date.toLocaleString();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Status badge with color
|
|
88
|
+
*/
|
|
89
|
+
export function statusBadge(status) {
|
|
90
|
+
const colors = {
|
|
91
|
+
pending: chalk.yellow,
|
|
92
|
+
processing: chalk.blue,
|
|
93
|
+
completed: chalk.green,
|
|
94
|
+
error: chalk.red,
|
|
95
|
+
failed: chalk.red
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const color = colors[status] || chalk.gray;
|
|
99
|
+
return color(status);
|
|
100
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MeetScribe API Client
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
import FormData from 'form-data';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import mime from 'mime-types';
|
|
10
|
+
|
|
11
|
+
export class MeetScribeClient {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.apiKey = options.apiKey;
|
|
14
|
+
this.baseUrl = options.baseUrl || 'https://api.meetscribe.ru/v1';
|
|
15
|
+
this.timeout = options.timeout || 300000; // 5 minutes default
|
|
16
|
+
|
|
17
|
+
this.client = axios.create({
|
|
18
|
+
baseURL: this.baseUrl,
|
|
19
|
+
timeout: this.timeout,
|
|
20
|
+
headers: {
|
|
21
|
+
'X-API-Key': this.apiKey,
|
|
22
|
+
'User-Agent': 'meetscribe-cli/1.0.0'
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Add response interceptor for error handling
|
|
27
|
+
this.client.interceptors.response.use(
|
|
28
|
+
response => response,
|
|
29
|
+
error => {
|
|
30
|
+
if (error.response) {
|
|
31
|
+
const { status, data } = error.response;
|
|
32
|
+
const message = data?.error?.message || data?.message || 'API Error';
|
|
33
|
+
const code = data?.error?.code || 'api_error';
|
|
34
|
+
|
|
35
|
+
const apiError = new Error(message);
|
|
36
|
+
apiError.code = code;
|
|
37
|
+
apiError.status = status;
|
|
38
|
+
apiError.response = error.response;
|
|
39
|
+
|
|
40
|
+
throw apiError;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (error.code === 'ECONNREFUSED') {
|
|
44
|
+
const connError = new Error('Cannot connect to API server');
|
|
45
|
+
connError.code = 'connection_error';
|
|
46
|
+
throw connError;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Upload and transcribe a file
|
|
56
|
+
*/
|
|
57
|
+
async transcribe(filePath, options = {}) {
|
|
58
|
+
const absolutePath = path.resolve(filePath);
|
|
59
|
+
|
|
60
|
+
if (!fs.existsSync(absolutePath)) {
|
|
61
|
+
throw new Error(`File not found: ${absolutePath}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const stat = fs.statSync(absolutePath);
|
|
65
|
+
const fileName = path.basename(absolutePath);
|
|
66
|
+
const mimeType = mime.lookup(absolutePath) || 'application/octet-stream';
|
|
67
|
+
|
|
68
|
+
const form = new FormData();
|
|
69
|
+
form.append('file', fs.createReadStream(absolutePath), {
|
|
70
|
+
filename: fileName,
|
|
71
|
+
contentType: mimeType,
|
|
72
|
+
knownLength: stat.size
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (options.language) {
|
|
76
|
+
form.append('language', options.language);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (options.speakers) {
|
|
80
|
+
form.append('speakers', String(options.speakers));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (options.webhookUrl) {
|
|
84
|
+
form.append('webhook_url', options.webhookUrl);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const response = await this.client.post('/transcribe', form, {
|
|
88
|
+
headers: {
|
|
89
|
+
...form.getHeaders()
|
|
90
|
+
},
|
|
91
|
+
maxContentLength: Infinity,
|
|
92
|
+
maxBodyLength: Infinity,
|
|
93
|
+
onUploadProgress: options.onProgress
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return response.data;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get job status
|
|
101
|
+
*/
|
|
102
|
+
async getJobStatus(jobId) {
|
|
103
|
+
const response = await this.client.get(`/jobs/${jobId}`);
|
|
104
|
+
return response.data;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Wait for job to complete
|
|
109
|
+
*/
|
|
110
|
+
async waitForJob(jobId, options = {}) {
|
|
111
|
+
const pollInterval = options.pollInterval || 3000;
|
|
112
|
+
const maxWait = options.maxWait || 3600000; // 1 hour
|
|
113
|
+
const onProgress = options.onProgress;
|
|
114
|
+
|
|
115
|
+
const startTime = Date.now();
|
|
116
|
+
|
|
117
|
+
while (true) {
|
|
118
|
+
const result = await this.getJobStatus(jobId);
|
|
119
|
+
|
|
120
|
+
if (onProgress) {
|
|
121
|
+
onProgress(result);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (result.data?.status === 'completed') {
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (result.data?.status === 'error' || result.data?.status === 'failed') {
|
|
129
|
+
throw new Error(result.data?.error || 'Job failed');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (Date.now() - startTime > maxWait) {
|
|
133
|
+
throw new Error('Timeout waiting for job completion');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get material details
|
|
142
|
+
*/
|
|
143
|
+
async getMaterial(materialId) {
|
|
144
|
+
const response = await this.client.get(`/materials/${materialId}`);
|
|
145
|
+
return response.data;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* List materials
|
|
150
|
+
*/
|
|
151
|
+
async listMaterials(options = {}) {
|
|
152
|
+
const params = new URLSearchParams();
|
|
153
|
+
|
|
154
|
+
if (options.page) params.append('page', options.page);
|
|
155
|
+
if (options.perPage) params.append('per_page', options.perPage);
|
|
156
|
+
if (options.status) params.append('status', options.status);
|
|
157
|
+
|
|
158
|
+
const response = await this.client.get(`/materials?${params}`);
|
|
159
|
+
return response.data;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get protocol for material
|
|
164
|
+
*/
|
|
165
|
+
async getProtocol(materialId) {
|
|
166
|
+
const response = await this.client.get(`/materials/${materialId}/protocol`);
|
|
167
|
+
return response.data;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get transcript for material
|
|
172
|
+
*/
|
|
173
|
+
async getTranscript(materialId) {
|
|
174
|
+
const response = await this.client.get(`/materials/${materialId}/transcript`);
|
|
175
|
+
return response.data;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get account info
|
|
180
|
+
*/
|
|
181
|
+
async getAccount() {
|
|
182
|
+
const response = await this.client.get('/account');
|
|
183
|
+
return response.data;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get usage statistics
|
|
188
|
+
*/
|
|
189
|
+
async getUsage() {
|
|
190
|
+
const response = await this.client.get('/account/usage');
|
|
191
|
+
return response.data;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* CLI Auth: Create session
|
|
196
|
+
*/
|
|
197
|
+
async createCliSession() {
|
|
198
|
+
const response = await this.client.post('/cli/session');
|
|
199
|
+
return response.data;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* CLI Auth: Poll for session status
|
|
204
|
+
*/
|
|
205
|
+
async pollCliToken(sessionId) {
|
|
206
|
+
const response = await this.client.get(`/cli/session/${sessionId}`);
|
|
207
|
+
return response.data;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export default MeetScribeClient;
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MeetScribe SDK TypeScript Definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface MeetScribeClientOptions {
|
|
6
|
+
apiKey: string;
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
timeout?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TranscribeOptions {
|
|
12
|
+
language?: 'ru' | 'en' | 'auto';
|
|
13
|
+
speakers?: number;
|
|
14
|
+
webhookUrl?: string;
|
|
15
|
+
onProgress?: (event: ProgressEvent) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ProgressEvent {
|
|
19
|
+
loaded: number;
|
|
20
|
+
total?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface JobStatus {
|
|
24
|
+
job_id: string;
|
|
25
|
+
status: 'pending' | 'processing' | 'completed' | 'error';
|
|
26
|
+
progress?: number;
|
|
27
|
+
status_details?: string;
|
|
28
|
+
error?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Material {
|
|
32
|
+
id: string;
|
|
33
|
+
title: string;
|
|
34
|
+
status: string;
|
|
35
|
+
has_protocol: boolean;
|
|
36
|
+
has_transcript: boolean;
|
|
37
|
+
created_at: string;
|
|
38
|
+
updated_at?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface Protocol {
|
|
42
|
+
material_id: string;
|
|
43
|
+
summary: string;
|
|
44
|
+
participants_list: Array<{ name: string; role?: string }>;
|
|
45
|
+
key_points: string[];
|
|
46
|
+
decisions: string[];
|
|
47
|
+
action_items: ActionItem[];
|
|
48
|
+
open_questions: string[];
|
|
49
|
+
next_meeting?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ActionItem {
|
|
53
|
+
id: string;
|
|
54
|
+
text: string;
|
|
55
|
+
assignee?: string;
|
|
56
|
+
deadline?: string;
|
|
57
|
+
priority: 'low' | 'medium' | 'high';
|
|
58
|
+
status: 'pending' | 'in_progress' | 'done' | 'cancelled';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface ApiResponse<T> {
|
|
62
|
+
success: boolean;
|
|
63
|
+
data?: T;
|
|
64
|
+
error?: {
|
|
65
|
+
code: string;
|
|
66
|
+
message: string;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class MeetScribeClient {
|
|
71
|
+
constructor(options: MeetScribeClientOptions);
|
|
72
|
+
|
|
73
|
+
transcribe(filePath: string, options?: TranscribeOptions): Promise<ApiResponse<{ job_id: string }>>;
|
|
74
|
+
|
|
75
|
+
getJobStatus(jobId: string): Promise<ApiResponse<JobStatus>>;
|
|
76
|
+
|
|
77
|
+
waitForJob(jobId: string, options?: {
|
|
78
|
+
pollInterval?: number;
|
|
79
|
+
maxWait?: number;
|
|
80
|
+
onProgress?: (status: JobStatus) => void;
|
|
81
|
+
}): Promise<ApiResponse<JobStatus>>;
|
|
82
|
+
|
|
83
|
+
getMaterial(materialId: string): Promise<ApiResponse<Material>>;
|
|
84
|
+
|
|
85
|
+
listMaterials(options?: {
|
|
86
|
+
page?: number;
|
|
87
|
+
perPage?: number;
|
|
88
|
+
status?: string;
|
|
89
|
+
}): Promise<ApiResponse<{ materials: Material[]; pagination: { page: number; total: number } }>>;
|
|
90
|
+
|
|
91
|
+
getProtocol(materialId: string): Promise<ApiResponse<Protocol>>;
|
|
92
|
+
|
|
93
|
+
getTranscript(materialId: string): Promise<ApiResponse<{ transcript: string }>>;
|
|
94
|
+
|
|
95
|
+
getAccount(): Promise<ApiResponse<{ username: string; email?: string; plan?: string }>>;
|
|
96
|
+
|
|
97
|
+
getUsage(): Promise<ApiResponse<{ today: number; total: number }>>;
|
|
98
|
+
}
|