From 3289e609027eb7a16f2065e409fc50c920e1533b Mon Sep 17 00:00:00 2001 From: twitchard Date: Thu, 18 Sep 2025 12:22:21 -0500 Subject: [PATCH 1/2] wip --- src/index.ts | 37 +++++++++ src/serve-access-token.ts | 156 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 src/serve-access-token.ts diff --git a/src/index.ts b/src/index.ts index 0b43b14..0112dcc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,6 +44,7 @@ import { showSession, login, } from './config'; +import { ServeAccessToken } from './serve-access-token'; // Create and run the CLI const cli = new Cli({ @@ -507,9 +508,45 @@ class LoginCommand extends Command { } } +class ServeAccessTokenCommand extends Command { + static paths = [['serve-access-token']]; + static usage = Command.Usage({ + description: 'Start a development server for access token generation', + details: `Starts a local HTTP server that provides an /access-token endpoint for development purposes. + +This server generates access tokens using your HUME_API_KEY and HUME_SECRET_KEY environment variables. + +⚠️ This is a demo server meant for development use only. On production servers, implement an /access-token endpoint on your own backend infrastructure, following the directions in https://dev.hume.ai/docs/introduction/api-key#token-authentication.`, + examples: [['Start the access token server', 'serve-access-token']], + }); + + port = Option.String('--port', { + description: 'Port to listen on (default: 8080)', + validator: t.isNumber(), + }); + + host = Option.String('--host', { + description: 'Host to listen on (default: localhost)', + }); + + apiKey = Option.String('--api-key', { + description: usageDescriptions.apiKey, + }); + + secretKey = Option.String('--secret-key', { + description: 'Override the default secret key', + }); + + async execute() { + const serveAccessToken = new ServeAccessToken(); + await serveAccessToken.serve(this); + } +} + cli.register(RootCommand); cli.register(TtsCommand); cli.register(LoginCommand); +cli.register(ServeAccessTokenCommand); cli.register(SessionRootCommand); cli.register(SaveVoiceCommand); cli.register(ListVoicesCommand); diff --git a/src/serve-access-token.ts b/src/serve-access-token.ts new file mode 100644 index 0000000..3c737f5 --- /dev/null +++ b/src/serve-access-token.ts @@ -0,0 +1,156 @@ +import { createServer } from 'http'; +import { fetchAccessToken } from 'hume/wrapper/fetchAccessToken'; +import { type CommonOpts, makeReporter, getSettings } from './common'; + +export interface ServeAccessTokenOpts extends CommonOpts { + port?: number; + host?: string; + secretKey?: string; +} + +export class ServeAccessToken { + async serve(opts: ServeAccessTokenOpts): Promise { + const { globalConfig, session, env, reporter } = await getSettings(opts); + + const port = opts.port ?? 8080; + const host = opts.host ?? 'localhost'; + + // Resolve API key and secret key with priority: globalConfig > session > env > opts + const apiKey = opts.apiKey ?? globalConfig.apiKey ?? session.apiKey ?? env.HUME_API_KEY; + const secretKey = opts.secretKey ?? env.HUME_SECRET_KEY; + + if (!apiKey || !secretKey) { + reporter.warn('⚠️ Missing required credentials:'); + if (!apiKey) { + reporter.warn(' API key not found. You can set it via:'); + reporter.warn(' - HUME_API_KEY environment variable'); + reporter.warn(' - --api-key command line option'); + reporter.warn(' - hume config set apiKey '); + } + if (!secretKey) { + reporter.warn(' Secret key not found. You can set it via:'); + reporter.warn(' - HUME_SECRET_KEY environment variable'); + reporter.warn(' - --secret-key command line option'); + } + reporter.warn(''); + reporter.warn('Example usage:'); + reporter.warn(' export HUME_API_KEY=your_api_key_here'); + reporter.warn(' export HUME_SECRET_KEY=your_secret_key_here'); + reporter.warn(' hume serve-access-token'); + reporter.warn(''); + reporter.warn('Or with command line options:'); + reporter.warn(' hume serve-access-token --api-key your_api_key --secret-key your_secret_key'); + process.exit(1); + } + + const server = createServer(async (req, res) => { + // Set CORS headers + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); + + // Handle preflight OPTIONS request + if (req.method === 'OPTIONS') { + res.writeHead(204); + res.end(); + return; + } + + if (req.url === '/access-token' && (req.method === 'GET' || req.method === 'POST')) { + try { + const accessToken = await fetchAccessToken({ + apiKey, + secretKey, + host: opts.baseUrl, + }); + + res.setHeader('Content-Type', 'application/json'); + res.writeHead(200); + res.end(JSON.stringify({ access_token: accessToken })); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + res.setHeader('Content-Type', 'application/json'); + res.writeHead(500); + res.end(JSON.stringify({ error: errorMessage })); + } + } else if (req.url === '/' && req.method === 'GET') { + // Serve a simple info page + const html = ` + + + + + Hume Access Token Server + + + +

Hume Access Token Server

+

This development server is running on http://${host}:${port}

+ +
+ ⚠️ Development Only: This server is meant for development use only. + On production servers, implement an /access-token endpoint on your own backend infrastructure. +
+ +

Available Endpoints

+ +
+ GET/POST /access-token
+ Returns a new access token in JSON format: {"access_token": "..."} +
+ +

Usage Example

+
fetch('http://${host}:${port}/access-token')
+  .then(response => response.json())
+  .then(data => console.log('Access Token:', data.access_token));
+ +

For more information, see the Hume API documentation.

+ +`; + + res.setHeader('Content-Type', 'text/html'); + res.writeHead(200); + res.end(html); + } else { + res.writeHead(404); + res.end('Not Found'); + } + }); + + server.listen(port, host, () => { + reporter.info(`🚀 Hume access token server listening on http://${host}:${port}`); + reporter.info(''); + reporter.info('Available endpoints:'); + reporter.info(` GET/POST http://${host}:${port}/access-token`); + reporter.info(''); + reporter.info('⚠️ This is a demo server meant for development use only.'); + reporter.info(' On production servers, implement an /access-token endpoint'); + reporter.info(' on your own backend infrastructure, following the directions at:'); + reporter.info(' https://dev.hume.ai/docs/introduction/api-key#token-authentication'); + reporter.info(''); + reporter.info('Press Ctrl+C to stop the server.'); + }); + + // Handle graceful shutdown + process.on('SIGINT', () => { + reporter.info('\n📡 Shutting down server...'); + server.close(() => { + reporter.info('✅ Server stopped.'); + process.exit(0); + }); + }); + + process.on('SIGTERM', () => { + reporter.info('\n📡 Shutting down server...'); + server.close(() => { + reporter.info('✅ Server stopped.'); + process.exit(0); + }); + }); + } +} From c9734771e1b63ec274389aa6eb492b2c437e8539 Mon Sep 17 00:00:00 2001 From: twitchard Date: Fri, 19 Sep 2025 13:33:43 -0500 Subject: [PATCH 2/2] wip --- package.json | 1 + src/serve-access-token.ts | 139 +++++++++++++------------------------- 2 files changed, 49 insertions(+), 91 deletions(-) diff --git a/package.json b/package.json index 28ab1ed..1b4daab 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "peerDependencies": { "typescript": "^5.0.0" }, + "main": "src/index.ts", "scripts": { "test": "bun test", "test:verbose": "BUN_TEST_VERBOSE=1 bun test", diff --git a/src/serve-access-token.ts b/src/serve-access-token.ts index 3c737f5..3d3ab01 100644 --- a/src/serve-access-token.ts +++ b/src/serve-access-token.ts @@ -1,6 +1,6 @@ import { createServer } from 'http'; import { fetchAccessToken } from 'hume/wrapper/fetchAccessToken'; -import { type CommonOpts, makeReporter, getSettings } from './common'; +import { type CommonOpts, getSettings } from './common'; export interface ServeAccessTokenOpts extends CommonOpts { port?: number; @@ -20,43 +20,40 @@ export class ServeAccessToken { const secretKey = opts.secretKey ?? env.HUME_SECRET_KEY; if (!apiKey || !secretKey) { - reporter.warn('⚠️ Missing required credentials:'); + let warningMessage = '⚠️ Missing required credentials:\n'; + if (!apiKey) { - reporter.warn(' API key not found. You can set it via:'); - reporter.warn(' - HUME_API_KEY environment variable'); - reporter.warn(' - --api-key command line option'); - reporter.warn(' - hume config set apiKey '); + warningMessage += ' API key not found. You can set it via:\n'; + warningMessage += ' - HUME_API_KEY environment variable\n'; + warningMessage += ' - --api-key command line option\n'; + warningMessage += ' - hume config set apiKey \n'; } + if (!secretKey) { - reporter.warn(' Secret key not found. You can set it via:'); - reporter.warn(' - HUME_SECRET_KEY environment variable'); - reporter.warn(' - --secret-key command line option'); + warningMessage += ' Secret key not found. You can set it via:\n'; + warningMessage += ' - HUME_SECRET_KEY environment variable\n'; + warningMessage += ' - --secret-key command line option\n'; } - reporter.warn(''); - reporter.warn('Example usage:'); - reporter.warn(' export HUME_API_KEY=your_api_key_here'); - reporter.warn(' export HUME_SECRET_KEY=your_secret_key_here'); - reporter.warn(' hume serve-access-token'); - reporter.warn(''); - reporter.warn('Or with command line options:'); - reporter.warn(' hume serve-access-token --api-key your_api_key --secret-key your_secret_key'); + + warningMessage += '\nExample usage:\n'; + warningMessage += ' export HUME_API_KEY=your_api_key_here\n'; + warningMessage += ' export HUME_SECRET_KEY=your_secret_key_here\n'; + warningMessage += ' hume serve-access-token\n\n'; + warningMessage += 'Or with command line options:\n'; + warningMessage += + ' hume serve-access-token --api-key your_api_key --secret-key your_secret_key'; + + reporter.warn(warningMessage); process.exit(1); } const server = createServer(async (req, res) => { // Set CORS headers res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Methods', 'GET'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); - // Handle preflight OPTIONS request - if (req.method === 'OPTIONS') { - res.writeHead(204); - res.end(); - return; - } - - if (req.url === '/access-token' && (req.method === 'GET' || req.method === 'POST')) { + if (req.url === '/access-token' && req.method === 'GET') { try { const accessToken = await fetchAccessToken({ apiKey, @@ -67,90 +64,50 @@ export class ServeAccessToken { res.setHeader('Content-Type', 'application/json'); res.writeHead(200); res.end(JSON.stringify({ access_token: accessToken })); + return; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; res.setHeader('Content-Type', 'application/json'); res.writeHead(500); res.end(JSON.stringify({ error: errorMessage })); + return; } - } else if (req.url === '/' && req.method === 'GET') { - // Serve a simple info page - const html = ` - - - - - Hume Access Token Server - - - -

Hume Access Token Server

-

This development server is running on http://${host}:${port}

- -
- ⚠️ Development Only: This server is meant for development use only. - On production servers, implement an /access-token endpoint on your own backend infrastructure. -
- -

Available Endpoints

- -
- GET/POST /access-token
- Returns a new access token in JSON format: {"access_token": "..."} -
- -

Usage Example

-
fetch('http://${host}:${port}/access-token')
-  .then(response => response.json())
-  .then(data => console.log('Access Token:', data.access_token));
- -

For more information, see the Hume API documentation.

- -`; - - res.setHeader('Content-Type', 'text/html'); + } + + if (req.url === '/' && req.method === 'GET') { + res.setHeader('Content-Type', 'text/plain'); res.writeHead(200); - res.end(html); - } else { - res.writeHead(404); - res.end('Not Found'); + res.end(infoMessage); + return; } + + res.writeHead(404); + res.end('Not Found'); }); + const infoMessage = `🚀 Hume access token server listening on http://${host}:${port} + +⚠️ This is a demo server meant for development use only. + On production servers, implement an /access-token endpoint + on your own backend infrastructure, following the directions at: + https://dev.hume.ai/docs/introduction/api-key#token-authentication + +Press Ctrl+C to stop the server.`; + server.listen(port, host, () => { - reporter.info(`🚀 Hume access token server listening on http://${host}:${port}`); - reporter.info(''); - reporter.info('Available endpoints:'); - reporter.info(` GET/POST http://${host}:${port}/access-token`); - reporter.info(''); - reporter.info('⚠️ This is a demo server meant for development use only.'); - reporter.info(' On production servers, implement an /access-token endpoint'); - reporter.info(' on your own backend infrastructure, following the directions at:'); - reporter.info(' https://dev.hume.ai/docs/introduction/api-key#token-authentication'); - reporter.info(''); - reporter.info('Press Ctrl+C to stop the server.'); + reporter.info(infoMessage); }); // Handle graceful shutdown - process.on('SIGINT', () => { + const shutdown = () => { reporter.info('\n📡 Shutting down server...'); server.close(() => { reporter.info('✅ Server stopped.'); process.exit(0); }); - }); + }; - process.on('SIGTERM', () => { - reporter.info('\n📡 Shutting down server...'); - server.close(() => { - reporter.info('✅ Server stopped.'); - process.exit(0); - }); - }); + process.on('SIGINT', shutdown); + process.on('SIGTERM', shutdown); } }