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/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..3d3ab01 --- /dev/null +++ b/src/serve-access-token.ts @@ -0,0 +1,113 @@ +import { createServer } from 'http'; +import { fetchAccessToken } from 'hume/wrapper/fetchAccessToken'; +import { type CommonOpts, 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) { + let warningMessage = '⚠️ Missing required credentials:\n'; + + if (!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) { + 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'; + } + + 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'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); + + if (req.url === '/access-token' && req.method === 'GET') { + 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 })); + 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; + } + } + + if (req.url === '/' && req.method === 'GET') { + res.setHeader('Content-Type', 'text/plain'); + res.writeHead(200); + 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(infoMessage); + }); + + // Handle graceful shutdown + const shutdown = () => { + reporter.info('\n📡 Shutting down server...'); + server.close(() => { + reporter.info('✅ Server stopped.'); + process.exit(0); + }); + }; + + process.on('SIGINT', shutdown); + process.on('SIGTERM', shutdown); + } +}