@hazeljs/cli 0.2.0-beta.26 → 0.2.0-beta.27
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/dist/commands/add.js
CHANGED
|
@@ -44,6 +44,10 @@ const HAZEL_PACKAGES = {
|
|
|
44
44
|
npm: '@hazeljs/rag',
|
|
45
45
|
hint: 'import { RAGPipeline } from "@hazeljs/rag";',
|
|
46
46
|
},
|
|
47
|
+
'pdf-to-audio': {
|
|
48
|
+
npm: '@hazeljs/pdf-to-audio',
|
|
49
|
+
hint: 'import { PdfToAudioModule } from "@hazeljs/pdf-to-audio";\n // PdfToAudioModule converts PDFs to audio via TTS',
|
|
50
|
+
},
|
|
47
51
|
serverless: {
|
|
48
52
|
npm: '@hazeljs/serverless',
|
|
49
53
|
hint: 'import { createLambdaHandler } from "@hazeljs/serverless";',
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.pdfToAudioCommand = pdfToAudioCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const form_data_1 = __importDefault(require("form-data"));
|
|
11
|
+
const DEFAULT_API_URL = process.env.HAZEL_API_URL || 'http://localhost:3000';
|
|
12
|
+
async function submitJob(apiUrl, filePath, options) {
|
|
13
|
+
const formData = new form_data_1.default();
|
|
14
|
+
formData.append('file', fs_1.default.createReadStream(filePath), path_1.default.basename(filePath));
|
|
15
|
+
if (options.voice)
|
|
16
|
+
formData.append('voice', options.voice);
|
|
17
|
+
if (options.includeSummary === false)
|
|
18
|
+
formData.append('includeSummary', 'false');
|
|
19
|
+
if (options.summaryOnly === true)
|
|
20
|
+
formData.append('summaryOnly', 'true');
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
const res = await fetch(`${apiUrl}/api/pdf-to-audio/convert`, {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
body: formData,
|
|
25
|
+
headers: formData.getHeaders(),
|
|
26
|
+
});
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
const text = await res.text();
|
|
29
|
+
throw new Error(`Submit failed (${res.status}): ${text}`);
|
|
30
|
+
}
|
|
31
|
+
const json = (await res.json());
|
|
32
|
+
if (!json.jobId)
|
|
33
|
+
throw new Error('No jobId in response');
|
|
34
|
+
return json.jobId;
|
|
35
|
+
}
|
|
36
|
+
async function getStatus(apiUrl, jobId) {
|
|
37
|
+
const res = await fetch(`${apiUrl}/api/pdf-to-audio/status/${encodeURIComponent(jobId)}`);
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
if (res.status === 404)
|
|
40
|
+
return null;
|
|
41
|
+
throw new Error(`Status failed (${res.status}): ${await res.text()}`);
|
|
42
|
+
}
|
|
43
|
+
return (await res.json());
|
|
44
|
+
}
|
|
45
|
+
async function downloadAudio(apiUrl, jobId) {
|
|
46
|
+
const res = await fetch(`${apiUrl}/api/pdf-to-audio/download/${encodeURIComponent(jobId)}`);
|
|
47
|
+
if (!res.ok)
|
|
48
|
+
throw new Error(`Download failed (${res.status}): ${await res.text()}`);
|
|
49
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
50
|
+
return Buffer.from(arrayBuffer);
|
|
51
|
+
}
|
|
52
|
+
function pdfToAudioCommand(program) {
|
|
53
|
+
const pdfToAudio = program
|
|
54
|
+
.command('pdf-to-audio')
|
|
55
|
+
.description('Convert PDF to audio via async job (submit, poll status, download)');
|
|
56
|
+
pdfToAudio
|
|
57
|
+
.command('convert <file.pdf>')
|
|
58
|
+
.description('Submit PDF for conversion, returns job ID')
|
|
59
|
+
.option('-o, --output <path>', 'Output MP3 path (used with --wait)')
|
|
60
|
+
.option('--voice <name>', 'TTS voice (alloy, echo, fable, onyx, nova, shimmer, ash, sage, coral)', 'alloy')
|
|
61
|
+
.option('--no-summary', 'Skip AI-generated document summary')
|
|
62
|
+
.option('--summary-only', 'Output only the summary, do not read the full document')
|
|
63
|
+
.option('--api-url <url>', 'API base URL', DEFAULT_API_URL)
|
|
64
|
+
.option('--wait', 'Poll until complete and download to --output')
|
|
65
|
+
.option('--poll-interval <ms>', 'Poll interval in ms', '3000')
|
|
66
|
+
.action(async (filePath, opts) => {
|
|
67
|
+
try {
|
|
68
|
+
if (!filePath?.toLowerCase().endsWith('.pdf')) {
|
|
69
|
+
console.log(chalk_1.default.red('✗ Please provide a PDF file path'));
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
const resolvedPath = path_1.default.isAbsolute(filePath) ? filePath : path_1.default.resolve(process.cwd(), filePath);
|
|
73
|
+
if (!fs_1.default.existsSync(resolvedPath)) {
|
|
74
|
+
console.log(chalk_1.default.red(`✗ File not found: ${resolvedPath}`));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
const apiUrl = (opts.apiUrl || DEFAULT_API_URL).replace(/\/$/, '');
|
|
78
|
+
const includeSummary = opts.summary !== false;
|
|
79
|
+
const summaryOnly = opts.summaryOnly === true;
|
|
80
|
+
console.log(chalk_1.default.blue('📄 Submitting PDF for conversion...'));
|
|
81
|
+
console.log(chalk_1.default.gray(` API: ${apiUrl}`));
|
|
82
|
+
console.log(chalk_1.default.gray(` File: ${resolvedPath}`));
|
|
83
|
+
console.log(chalk_1.default.gray(` Voice: ${opts.voice || 'alloy'}`));
|
|
84
|
+
if (summaryOnly)
|
|
85
|
+
console.log(chalk_1.default.gray(` Mode: summary only`));
|
|
86
|
+
const jobId = await submitJob(apiUrl, resolvedPath, {
|
|
87
|
+
voice: opts.voice,
|
|
88
|
+
includeSummary,
|
|
89
|
+
summaryOnly,
|
|
90
|
+
});
|
|
91
|
+
console.log(chalk_1.default.green(`\n✓ Job submitted: ${jobId}`));
|
|
92
|
+
if (opts.wait) {
|
|
93
|
+
const outputPath = opts.output ||
|
|
94
|
+
path_1.default.join(path_1.default.dirname(resolvedPath), path_1.default.basename(resolvedPath, '.pdf') + '.mp3');
|
|
95
|
+
const interval = parseInt(opts.pollInterval || '3000', 10);
|
|
96
|
+
console.log(chalk_1.default.gray(`\nPolling every ${interval}ms...`));
|
|
97
|
+
// eslint-disable-next-line no-constant-condition
|
|
98
|
+
while (true) {
|
|
99
|
+
const status = await getStatus(apiUrl, jobId);
|
|
100
|
+
if (!status) {
|
|
101
|
+
console.log(chalk_1.default.red('✗ Job not found or expired'));
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
const pct = status.progress ?? 0;
|
|
105
|
+
const msg = status.message ? ` - ${status.message}` : '';
|
|
106
|
+
process.stdout.write(chalk_1.default.gray(` Status: ${status.status} ${pct}%${msg}`));
|
|
107
|
+
if (status.status === 'completed') {
|
|
108
|
+
console.log(chalk_1.default.gray(' done'));
|
|
109
|
+
const audio = await downloadAudio(apiUrl, jobId);
|
|
110
|
+
fs_1.default.writeFileSync(outputPath, audio);
|
|
111
|
+
console.log(chalk_1.default.green(`\n✓ Saved: ${outputPath}`));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (status.status === 'failed') {
|
|
115
|
+
console.log(chalk_1.default.red(`\n✗ Conversion failed: ${status.error || 'Unknown error'}`));
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
console.log('');
|
|
119
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
console.log(chalk_1.default.gray('\nCheck status: hazel pdf-to-audio status ' + jobId + ' --api-url ' + apiUrl));
|
|
123
|
+
if (opts.output) {
|
|
124
|
+
console.log(chalk_1.default.gray('Download when ready: hazel pdf-to-audio status ' + jobId + ' -o ' + opts.output + ' --api-url ' + apiUrl));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
console.error(chalk_1.default.red('\n✗ Error:'), error instanceof Error ? error.message : error);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
pdfToAudio
|
|
133
|
+
.command('status <jobId>')
|
|
134
|
+
.description('Check job status and optionally download when ready')
|
|
135
|
+
.option('-o, --output <path>', 'Download audio to this path when completed')
|
|
136
|
+
.option('--api-url <url>', 'API base URL', DEFAULT_API_URL)
|
|
137
|
+
.action(async (jobId, opts) => {
|
|
138
|
+
try {
|
|
139
|
+
const apiUrl = (opts.apiUrl || DEFAULT_API_URL).replace(/\/$/, '');
|
|
140
|
+
const status = await getStatus(apiUrl, jobId);
|
|
141
|
+
if (!status) {
|
|
142
|
+
console.log(chalk_1.default.red('✗ Job not found or expired'));
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
console.log(chalk_1.default.blue('Job status:'));
|
|
146
|
+
console.log(chalk_1.default.gray(` ID: ${status.jobId}`));
|
|
147
|
+
console.log(chalk_1.default.gray(` Status: ${status.status}`));
|
|
148
|
+
console.log(chalk_1.default.gray(` Progress: ${status.progress}%`));
|
|
149
|
+
if (status.message)
|
|
150
|
+
console.log(chalk_1.default.gray(` Message: ${status.message}`));
|
|
151
|
+
if (status.totalChunks != null) {
|
|
152
|
+
console.log(chalk_1.default.gray(` Chunks: ${status.completedChunks ?? 0}/${status.totalChunks}`));
|
|
153
|
+
}
|
|
154
|
+
if (status.error)
|
|
155
|
+
console.log(chalk_1.default.red(` Error: ${status.error}`));
|
|
156
|
+
if (status.status === 'completed' && opts.output) {
|
|
157
|
+
const audio = await downloadAudio(apiUrl, jobId);
|
|
158
|
+
fs_1.default.writeFileSync(opts.output, audio);
|
|
159
|
+
console.log(chalk_1.default.green(`\n✓ Downloaded: ${opts.output}`));
|
|
160
|
+
}
|
|
161
|
+
else if (status.status === 'completed' && !opts.output) {
|
|
162
|
+
console.log(chalk_1.default.gray('\nDownload: GET ' + apiUrl + '/api/pdf-to-audio/download/' + jobId));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
console.error(chalk_1.default.red('\n✗ Error:'), error instanceof Error ? error.message : error);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -27,6 +27,7 @@ const generate_discovery_1 = require("./commands/generate-discovery");
|
|
|
27
27
|
const info_1 = require("./commands/info");
|
|
28
28
|
const add_1 = require("./commands/add");
|
|
29
29
|
const build_1 = require("./commands/build");
|
|
30
|
+
const pdf_to_audio_1 = require("./commands/pdf-to-audio");
|
|
30
31
|
const start_1 = require("./commands/start");
|
|
31
32
|
const test_1 = require("./commands/test");
|
|
32
33
|
const program = new commander_1.Command();
|
|
@@ -40,6 +41,7 @@ program
|
|
|
40
41
|
(0, info_1.infoCommand)(program);
|
|
41
42
|
(0, add_1.addCommand)(program);
|
|
42
43
|
(0, build_1.buildCommand)(program);
|
|
44
|
+
(0, pdf_to_audio_1.pdfToAudioCommand)(program);
|
|
43
45
|
(0, start_1.startCommand)(program);
|
|
44
46
|
(0, test_1.testCommand)(program);
|
|
45
47
|
// Generate command group
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hazeljs/cli",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.27",
|
|
4
4
|
"description": "Command-line interface for scaffolding and generating HazelJS applications and components",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"chalk": "^4.1.2",
|
|
23
23
|
"commander": "^11.1.0",
|
|
24
|
+
"form-data": "^4.0.0",
|
|
24
25
|
"inquirer": "^8.2.6",
|
|
25
26
|
"mustache": "^4.2.0",
|
|
26
27
|
"ora": "^5.4.1"
|
|
@@ -68,5 +69,5 @@
|
|
|
68
69
|
"type": "opencollective",
|
|
69
70
|
"url": "https://opencollective.com/hazeljs"
|
|
70
71
|
},
|
|
71
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "7ea9c4fdc8acf9a6d91722b7fd92b6bbd376e9e8"
|
|
72
73
|
}
|