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.
@@ -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,7 @@
1
+ /**
2
+ * MeetScribe SDK
3
+ * Programmatic access to MeetScribe API
4
+ */
5
+
6
+ export { MeetScribeClient } from './sdk/client.js';
7
+ export { default as config } from './cli/utils/config.js';
@@ -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;
@@ -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
+ }