mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
refactor: rename configuration variables for clarity
Standardize naming across CLI and server components: - CLI: serverUrl → apiUrl, HAPI_SERVER_URL → HAPI_API_URL - Server: webapp* → listen*, miniAppUrl → publicUrl, WEBAPP_* → HAPI_LISTEN_*, WEBAPP_URL → HAPI_PUBLIC_URL - Rename serverUrlInit.ts → apiUrlInit.ts with updated logic for backward compatibility - Update all imports, function calls, and documentation accordingly
This commit is contained in:
@@ -16,7 +16,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
{
|
{
|
||||||
echo "HAPI_HOME=~/.hapi-dev-test"
|
echo "HAPI_HOME=~/.hapi-dev-test"
|
||||||
echo "HAPI_SERVER_URL=http://localhost:3006"
|
echo "HAPI_API_URL=http://localhost:3006"
|
||||||
echo "CLI_API_TOKEN=${CLI_API_TOKEN:-dev-test-token}"
|
echo "CLI_API_TOKEN=${CLI_API_TOKEN:-dev-test-token}"
|
||||||
echo "HAPI_DAEMON_HTTP_TIMEOUT=60000"
|
echo "HAPI_DAEMON_HTTP_TIMEOUT=60000"
|
||||||
echo "HAPI_DAEMON_HEARTBEAT_INTERVAL=30000"
|
echo "HAPI_DAEMON_HEARTBEAT_INTERVAL=30000"
|
||||||
|
|||||||
+1
-1
@@ -139,7 +139,7 @@ User interface components.
|
|||||||
hapi runner start
|
hapi runner start
|
||||||
|
|
||||||
# With custom bot URL (for local development):
|
# With custom bot URL (for local development):
|
||||||
HAPI_SERVER_URL=http://localhost:3006 CLI_API_TOKEN=your_token hapi runner start
|
HAPI_API_URL=http://localhost:3006 CLI_API_TOKEN=your_token hapi runner start
|
||||||
|
|
||||||
# Stop the runner:
|
# Stop the runner:
|
||||||
hapi runner stop
|
hapi runner stop
|
||||||
|
|||||||
+1
-1
@@ -67,7 +67,7 @@ See `src/configuration.ts` for all options.
|
|||||||
### Required
|
### Required
|
||||||
|
|
||||||
- `CLI_API_TOKEN` - Shared secret; must match the server. Can be set via env or `~/.hapi/settings.json` (env wins).
|
- `CLI_API_TOKEN` - Shared secret; must match the server. Can be set via env or `~/.hapi/settings.json` (env wins).
|
||||||
- `HAPI_SERVER_URL` - Server base URL (default: http://localhost:3006).
|
- `HAPI_API_URL` - Server base URL (default: http://localhost:3006).
|
||||||
|
|
||||||
### Optional
|
### Optional
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -19,7 +19,7 @@ export class ApiClient {
|
|||||||
state: AgentState | null
|
state: AgentState | null
|
||||||
}): Promise<Session> {
|
}): Promise<Session> {
|
||||||
const response = await axios.post<CreateSessionResponse>(
|
const response = await axios.post<CreateSessionResponse>(
|
||||||
`${configuration.serverUrl}/cli/sessions`,
|
`${configuration.apiUrl}/cli/sessions`,
|
||||||
{
|
{
|
||||||
tag: opts.tag,
|
tag: opts.tag,
|
||||||
metadata: opts.metadata,
|
metadata: opts.metadata,
|
||||||
@@ -79,7 +79,7 @@ export class ApiClient {
|
|||||||
runnerState?: RunnerState
|
runnerState?: RunnerState
|
||||||
}): Promise<Machine> {
|
}): Promise<Machine> {
|
||||||
const response = await axios.post<CreateMachineResponse>(
|
const response = await axios.post<CreateMachineResponse>(
|
||||||
`${configuration.serverUrl}/cli/machines`,
|
`${configuration.apiUrl}/cli/machines`,
|
||||||
{
|
{
|
||||||
id: opts.machineId,
|
id: opts.machineId,
|
||||||
metadata: opts.metadata,
|
metadata: opts.metadata,
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ export class ApiMachineClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
connect(): void {
|
connect(): void {
|
||||||
this.socket = io(`${configuration.serverUrl}/cli`, {
|
this.socket = io(`${configuration.apiUrl}/cli`, {
|
||||||
transports: ['websocket'],
|
transports: ['websocket'],
|
||||||
auth: {
|
auth: {
|
||||||
token: this.token,
|
token: this.token,
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export class ApiSessionClient extends EventEmitter {
|
|||||||
registerCommonHandlers(this.rpcHandlerManager, this.metadata.path)
|
registerCommonHandlers(this.rpcHandlerManager, this.metadata.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.socket = io(`${configuration.serverUrl}/cli`, {
|
this.socket = io(`${configuration.apiUrl}/cli`, {
|
||||||
auth: {
|
auth: {
|
||||||
token: this.token,
|
token: this.token,
|
||||||
clientType: 'session-scoped' as const,
|
clientType: 'session-scoped' as const,
|
||||||
@@ -270,7 +270,7 @@ export class ApiSessionClient extends EventEmitter {
|
|||||||
let cursor = startSeq
|
let cursor = startSeq
|
||||||
while (true) {
|
while (true) {
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
`${configuration.serverUrl}/cli/sessions/${encodeURIComponent(this.sessionId)}/messages`,
|
`${configuration.apiUrl}/cli/sessions/${encodeURIComponent(this.sessionId)}/messages`,
|
||||||
{
|
{
|
||||||
params: { afterSeq: cursor, limit },
|
params: { afterSeq: cursor, limit },
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export async function handleAuthCommand(args: string[]): Promise<void> {
|
|||||||
const hasToken = Boolean(envToken || settingsToken)
|
const hasToken = Boolean(envToken || settingsToken)
|
||||||
const tokenSource = envToken ? 'environment' : (settingsToken ? 'settings file' : 'none')
|
const tokenSource = envToken ? 'environment' : (settingsToken ? 'settings file' : 'none')
|
||||||
console.log(chalk.bold('\nDirect Connect Status\n'))
|
console.log(chalk.bold('\nDirect Connect Status\n'))
|
||||||
console.log(chalk.gray(` HAPI_SERVER_URL: ${configuration.serverUrl}`))
|
console.log(chalk.gray(` HAPI_API_URL: ${configuration.apiUrl}`))
|
||||||
console.log(chalk.gray(` CLI_API_TOKEN: ${hasToken ? 'set' : 'missing'}`))
|
console.log(chalk.gray(` CLI_API_TOKEN: ${hasToken ? 'set' : 'missing'}`))
|
||||||
console.log(chalk.gray(` Token Source: ${tokenSource}`))
|
console.log(chalk.gray(` Token Source: ${tokenSource}`))
|
||||||
console.log(chalk.gray(` Machine ID: ${settings.machineId ?? 'not set'}`))
|
console.log(chalk.gray(` Machine ID: ${settings.machineId ?? 'not set'}`))
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ ${chalk.bold.cyan('Claude Code Options (from `claude --help`):')}
|
|||||||
messageLower.includes('network error')
|
messageLower.includes('network error')
|
||||||
) {
|
) {
|
||||||
console.error(chalk.yellow('Unable to connect to HAPI server'))
|
console.error(chalk.yellow('Unable to connect to HAPI server'))
|
||||||
console.error(chalk.gray(` Server URL: ${configuration.serverUrl}`))
|
console.error(chalk.gray(` Server URL: ${configuration.apiUrl}`))
|
||||||
console.error(chalk.gray(' Please check your network connection or server status'))
|
console.error(chalk.gray(' Please check your network connection or server status'))
|
||||||
} else if (httpStatus === 403 && responseErrorText === 'Machine access denied') {
|
} else if (httpStatus === 403 && responseErrorText === 'Machine access denied') {
|
||||||
console.error(chalk.red('Machine access denied.'))
|
console.error(chalk.red('Machine access denied.'))
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import packageJson from '../package.json'
|
|||||||
import { getCliArgs } from '@/utils/cliArgs'
|
import { getCliArgs } from '@/utils/cliArgs'
|
||||||
|
|
||||||
class Configuration {
|
class Configuration {
|
||||||
private _serverUrl: string
|
private _apiUrl: string
|
||||||
private _cliApiToken: string
|
private _cliApiToken: string
|
||||||
public readonly isRunnerProcess: boolean
|
public readonly isRunnerProcess: boolean
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ class Configuration {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Server configuration
|
// Server configuration
|
||||||
this._serverUrl = process.env.HAPI_SERVER_URL || 'http://localhost:3006'
|
this._apiUrl = process.env.HAPI_API_URL || 'http://localhost:3006'
|
||||||
this._cliApiToken = process.env.CLI_API_TOKEN || ''
|
this._cliApiToken = process.env.CLI_API_TOKEN || ''
|
||||||
|
|
||||||
// Check if we're running as runner based on process args
|
// Check if we're running as runner based on process args
|
||||||
@@ -64,12 +64,12 @@ class Configuration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get serverUrl(): string {
|
get apiUrl(): string {
|
||||||
return this._serverUrl
|
return this._apiUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
_setServerUrl(url: string): void {
|
_setApiUrl(url: string): void {
|
||||||
this._serverUrl = url
|
this._apiUrl = url
|
||||||
}
|
}
|
||||||
|
|
||||||
get cliApiToken(): string {
|
get cliApiToken(): string {
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ interface Settings {
|
|||||||
machineIdConfirmedByServer?: boolean
|
machineIdConfirmedByServer?: boolean
|
||||||
runnerAutoStartWhenRunningHappy?: boolean
|
runnerAutoStartWhenRunningHappy?: boolean
|
||||||
cliApiToken?: string
|
cliApiToken?: string
|
||||||
// Server URL for API connections (priority: env HAPI_SERVER_URL > this > default)
|
// API URL for server connections (priority: env HAPI_API_URL > this > default)
|
||||||
|
apiUrl?: string
|
||||||
|
// Legacy field name (for migration, read-only)
|
||||||
serverUrl?: string
|
serverUrl?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
*
|
*
|
||||||
* The integration test environment uses .env.integration-test which sets:
|
* The integration test environment uses .env.integration-test which sets:
|
||||||
* - HAPI_HOME=~/.hapi-dev-test (DIFFERENT from dev's ~/.hapi-dev!)
|
* - HAPI_HOME=~/.hapi-dev-test (DIFFERENT from dev's ~/.hapi-dev!)
|
||||||
* - HAPI_SERVER_URL=http://localhost:3006 (local hapi-server)
|
* - HAPI_API_URL=http://localhost:3006 (local hapi-server)
|
||||||
* - CLI_API_TOKEN=... (must match the server)
|
* - CLI_API_TOKEN=... (must match the server)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ async function isServerHealthy(): Promise<boolean> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = `${configuration.serverUrl}/cli/machines/__healthcheck__`;
|
const url = `${configuration.apiUrl}/cli/machines/__healthcheck__`;
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
headers: { Authorization: `Bearer ${configuration.cliApiToken}` },
|
headers: { Authorization: `Bearer ${configuration.cliApiToken}` },
|
||||||
signal: AbortSignal.timeout(1000)
|
signal: AbortSignal.timeout(1000)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const DEFAULT_IDLE_TIMEOUT_MS = 15 * 60_000
|
|||||||
const DEFAULT_MAX_TERMINALS = 4
|
const DEFAULT_MAX_TERMINALS = 4
|
||||||
const SENSITIVE_ENV_KEYS = new Set([
|
const SENSITIVE_ENV_KEYS = new Set([
|
||||||
'CLI_API_TOKEN',
|
'CLI_API_TOKEN',
|
||||||
'HAPI_SERVER_URL',
|
'HAPI_API_URL',
|
||||||
'HAPI_HTTP_MCP_URL',
|
'HAPI_HTTP_MCP_URL',
|
||||||
'TELEGRAM_BOT_TOKEN',
|
'TELEGRAM_BOT_TOKEN',
|
||||||
'OPENAI_API_KEY',
|
'OPENAI_API_KEY',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Server URL initialization module
|
* API URL initialization module
|
||||||
*
|
*
|
||||||
* Handles HAPI_SERVER_URL initialization with priority:
|
* Handles HAPI_API_URL initialization with priority:
|
||||||
* 1. Environment variable (highest - allows temporary override)
|
* 1. Environment variable (highest - allows temporary override)
|
||||||
* 2. Settings file (~/.hapi/settings.json)
|
* 2. Settings file (~/.hapi/settings.json)
|
||||||
* 3. Default value (http://localhost:3006)
|
* 3. Default value (http://localhost:3006)
|
||||||
@@ -11,19 +11,24 @@ import { configuration } from '@/configuration'
|
|||||||
import { readSettings } from '@/persistence'
|
import { readSettings } from '@/persistence'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize server URL
|
* Initialize API URL
|
||||||
* Must be called before any API operations
|
* Must be called before any API operations
|
||||||
*/
|
*/
|
||||||
export async function initializeServerUrl(): Promise<void> {
|
export async function initializeApiUrl(): Promise<void> {
|
||||||
// 1. Environment variable has highest priority (allows temporary override)
|
// 1. Environment variable has highest priority (allows temporary override)
|
||||||
if (process.env.HAPI_SERVER_URL) {
|
if (process.env.HAPI_API_URL) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Read from settings file
|
// 2. Read from settings file (new name first, then legacy)
|
||||||
const settings = await readSettings()
|
const settings = await readSettings()
|
||||||
|
if (settings.apiUrl) {
|
||||||
|
configuration._setApiUrl(settings.apiUrl)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (settings.serverUrl) {
|
if (settings.serverUrl) {
|
||||||
configuration._setServerUrl(settings.serverUrl)
|
// Migrate from legacy field name
|
||||||
|
configuration._setApiUrl(settings.serverUrl)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ export function getEnvironmentInfo(): Record<string, any> {
|
|||||||
return {
|
return {
|
||||||
PWD: process.env.PWD,
|
PWD: process.env.PWD,
|
||||||
HAPI_HOME: process.env.HAPI_HOME,
|
HAPI_HOME: process.env.HAPI_HOME,
|
||||||
HAPI_SERVER_URL: process.env.HAPI_SERVER_URL,
|
HAPI_API_URL: process.env.HAPI_API_URL,
|
||||||
HAPI_PROJECT_ROOT: process.env.HAPI_PROJECT_ROOT,
|
HAPI_PROJECT_ROOT: process.env.HAPI_PROJECT_ROOT,
|
||||||
CLI_API_TOKEN_SET: Boolean(process.env.CLI_API_TOKEN),
|
CLI_API_TOKEN_SET: Boolean(process.env.CLI_API_TOKEN),
|
||||||
DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING: process.env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING,
|
DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING: process.env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING,
|
||||||
@@ -33,7 +33,7 @@ export function getEnvironmentInfo(): Record<string, any> {
|
|||||||
workingDirectory: process.cwd(),
|
workingDirectory: process.cwd(),
|
||||||
processArgv: process.argv,
|
processArgv: process.argv,
|
||||||
happyDir: configuration?.happyHomeDir,
|
happyDir: configuration?.happyHomeDir,
|
||||||
serverUrl: configuration?.serverUrl,
|
apiUrl: configuration?.apiUrl,
|
||||||
logsDir: configuration?.logsDir,
|
logsDir: configuration?.logsDir,
|
||||||
processPid: process.pid,
|
processPid: process.pid,
|
||||||
nodeVersion: process.version,
|
nodeVersion: process.version,
|
||||||
@@ -107,14 +107,14 @@ export async function runDoctorCommand(filter?: 'all' | 'runner'): Promise<void>
|
|||||||
// Configuration
|
// Configuration
|
||||||
console.log(chalk.bold('⚙️ Configuration'));
|
console.log(chalk.bold('⚙️ Configuration'));
|
||||||
console.log(`hapi Home: ${chalk.blue(configuration.happyHomeDir)}`);
|
console.log(`hapi Home: ${chalk.blue(configuration.happyHomeDir)}`);
|
||||||
console.log(`Bot URL: ${chalk.blue(configuration.serverUrl)}`);
|
console.log(`Bot URL: ${chalk.blue(configuration.apiUrl)}`);
|
||||||
console.log(`Logs Dir: ${chalk.blue(configuration.logsDir)}`);
|
console.log(`Logs Dir: ${chalk.blue(configuration.logsDir)}`);
|
||||||
|
|
||||||
// Environment
|
// Environment
|
||||||
console.log(chalk.bold('\n🌍 Environment Variables'));
|
console.log(chalk.bold('\n🌍 Environment Variables'));
|
||||||
const env = getEnvironmentInfo();
|
const env = getEnvironmentInfo();
|
||||||
console.log(`HAPI_HOME: ${env.HAPI_HOME ? chalk.green(env.HAPI_HOME) : chalk.gray('not set')}`);
|
console.log(`HAPI_HOME: ${env.HAPI_HOME ? chalk.green(env.HAPI_HOME) : chalk.gray('not set')}`);
|
||||||
console.log(`HAPI_SERVER_URL: ${env.HAPI_SERVER_URL ? chalk.green(env.HAPI_SERVER_URL) : chalk.gray('not set')}`);
|
console.log(`HAPI_API_URL: ${env.HAPI_API_URL ? chalk.green(env.HAPI_API_URL) : chalk.gray('not set')}`);
|
||||||
console.log(`CLI_API_TOKEN: ${env.CLI_API_TOKEN_SET ? chalk.green('set') : chalk.gray('not set')}`);
|
console.log(`CLI_API_TOKEN: ${env.CLI_API_TOKEN_SET ? chalk.green('set') : chalk.gray('not set')}`);
|
||||||
console.log(`DANGEROUSLY_LOG_TO_SERVER: ${env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING ? chalk.yellow('ENABLED') : chalk.gray('not set')}`);
|
console.log(`DANGEROUSLY_LOG_TO_SERVER: ${env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING ? chalk.yellow('ENABLED') : chalk.gray('not set')}`);
|
||||||
console.log(`DEBUG: ${env.DEBUG ? chalk.green(env.DEBUG) : chalk.gray('not set')}`);
|
console.log(`DEBUG: ${env.DEBUG ? chalk.green(env.DEBUG) : chalk.gray('not set')}`);
|
||||||
|
|||||||
@@ -50,10 +50,10 @@ class Logger {
|
|||||||
constructor(
|
constructor(
|
||||||
public readonly logFilePath = getSessionLogPath()
|
public readonly logFilePath = getSessionLogPath()
|
||||||
) {
|
) {
|
||||||
// Remote logging enabled only when explicitly set with server URL
|
// Remote logging enabled only when explicitly set with API URL
|
||||||
if (process.env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING
|
if (process.env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING
|
||||||
&& process.env.HAPI_SERVER_URL) {
|
&& process.env.HAPI_API_URL) {
|
||||||
this.dangerouslyUnencryptedServerLoggingUrl = process.env.HAPI_SERVER_URL
|
this.dangerouslyUnencryptedServerLoggingUrl = process.env.HAPI_API_URL
|
||||||
console.log(chalk.yellow('[REMOTE LOGGING] Sending logs to server for AI debugging'))
|
console.log(chalk.yellow('[REMOTE LOGGING] Sending logs to server for AI debugging'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,15 +12,15 @@ import { stdin as input, stdout as output } from 'node:process'
|
|||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
import { configuration } from '@/configuration'
|
import { configuration } from '@/configuration'
|
||||||
import { readSettings, updateSettings } from '@/persistence'
|
import { readSettings, updateSettings } from '@/persistence'
|
||||||
import { initializeServerUrl } from '@/ui/serverUrlInit'
|
import { initializeApiUrl } from '@/ui/apiUrlInit'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize CLI API token
|
* Initialize CLI API token
|
||||||
* Must be called before any API operations
|
* Must be called before any API operations
|
||||||
*/
|
*/
|
||||||
export async function initializeToken(): Promise<void> {
|
export async function initializeToken(): Promise<void> {
|
||||||
// Initialize server URL first (env > settings.json > default)
|
// Initialize API URL first (env > settings.json > default)
|
||||||
await initializeServerUrl()
|
await initializeApiUrl()
|
||||||
|
|
||||||
// 1. Environment variable has highest priority (allows temporary override)
|
// 1. Environment variable has highest priority (allows temporary override)
|
||||||
if (configuration.cliApiToken) {
|
if (configuration.cliApiToken) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* Automatically starts the HAPI server when CLI is launched
|
* Automatically starts the HAPI server when CLI is launched
|
||||||
* if specific conditions are met:
|
* if specific conditions are met:
|
||||||
* 1. HAPI_SERVER_URL is not set (using default localhost:3006)
|
* 1. HAPI_API_URL is not set (using default localhost:3006)
|
||||||
* 2. cliApiToken exists in settings.json (server was previously started)
|
* 2. cliApiToken exists in settings.json (server was previously started)
|
||||||
* 3. Port 3006 is not currently listening
|
* 3. Port 3006 is not currently listening
|
||||||
*/
|
*/
|
||||||
@@ -90,18 +90,18 @@ async function waitForServerReady(
|
|||||||
* Determine if server should be auto-started
|
* Determine if server should be auto-started
|
||||||
*/
|
*/
|
||||||
async function shouldAutoStartServer(): Promise<boolean> {
|
async function shouldAutoStartServer(): Promise<boolean> {
|
||||||
// Condition 1: HAPI_SERVER_URL not set (using default localhost:3006)
|
// Condition 1: HAPI_API_URL not set (using default localhost:3006)
|
||||||
if (process.env.HAPI_SERVER_URL) {
|
if (process.env.HAPI_API_URL) {
|
||||||
logger.debug('[AUTO-START] HAPI_SERVER_URL is set, skipping auto-start')
|
logger.debug('[AUTO-START] HAPI_API_URL is set, skipping auto-start')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Condition 2: Check settings.json
|
// Condition 2: Check settings.json
|
||||||
const settings = await readSettings()
|
const settings = await readSettings()
|
||||||
|
|
||||||
// 2a: serverUrl is set in settings.json (user configured a specific server)
|
// 2a: apiUrl is set in settings.json (user configured a specific server)
|
||||||
if (settings.serverUrl) {
|
if (settings.apiUrl || settings.serverUrl) {
|
||||||
logger.debug('[AUTO-START] serverUrl is set in settings.json, skipping auto-start')
|
logger.debug('[AUTO-START] apiUrl is set in settings.json, skipping auto-start')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ export async function maybeAutoStartServer(): Promise<void> {
|
|||||||
|
|
||||||
startServerAsChild()
|
startServerAsChild()
|
||||||
|
|
||||||
const isReady = await waitForServerReady(configuration.serverUrl)
|
const isReady = await waitForServerReady(configuration.apiUrl)
|
||||||
|
|
||||||
if (!isReady) {
|
if (!isReady) {
|
||||||
console.log(chalk.yellow('Warning: Server did not start within expected time'))
|
console.log(chalk.yellow('Warning: Server did not start within expected time'))
|
||||||
|
|||||||
+1
-1
@@ -115,7 +115,7 @@ Only if they have your access token. For additional security:
|
|||||||
|
|
||||||
- Ensure server is running: `hapi server`
|
- Ensure server is running: `hapi server`
|
||||||
- Check firewall allows port 3006
|
- Check firewall allows port 3006
|
||||||
- Verify `HAPI_SERVER_URL` is correct
|
- Verify `HAPI_API_URL` is correct
|
||||||
|
|
||||||
### "Invalid token" error
|
### "Invalid token" error
|
||||||
|
|
||||||
|
|||||||
@@ -106,8 +106,10 @@ On first run, HAPI:
|
|||||||
| Variable | Default | Description |
|
| Variable | Default | Description |
|
||||||
|----------|---------|-------------|
|
|----------|---------|-------------|
|
||||||
| `CLI_API_TOKEN` | Auto-generated | Shared secret for authentication |
|
| `CLI_API_TOKEN` | Auto-generated | Shared secret for authentication |
|
||||||
| `HAPI_SERVER_URL` | `http://localhost:3006` | Server URL for CLI |
|
| `HAPI_API_URL` | `http://localhost:3006` | Server URL for CLI |
|
||||||
| `WEBAPP_PORT` | `3006` | HTTP server port |
|
| `HAPI_LISTEN_HOST` | `127.0.0.1` | HTTP server bind address |
|
||||||
|
| `HAPI_LISTEN_PORT` | `3006` | HTTP server port |
|
||||||
|
| `HAPI_PUBLIC_URL` | - | Public URL for external access |
|
||||||
| `HAPI_HOME` | `~/.hapi` | Config directory path |
|
| `HAPI_HOME` | `~/.hapi` | Config directory path |
|
||||||
| `DB_PATH` | `~/.hapi/hapi.db` | Database file path |
|
| `DB_PATH` | `~/.hapi/hapi.db` | Database file path |
|
||||||
| `CORS_ORIGINS` | - | Allowed CORS origins |
|
| `CORS_ORIGINS` | - | Allowed CORS origins |
|
||||||
@@ -118,7 +120,7 @@ On first run, HAPI:
|
|||||||
If the server is not on localhost, set these before running `hapi`:
|
If the server is not on localhost, set these before running `hapi`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export HAPI_SERVER_URL="http://your-server:3006"
|
export HAPI_API_URL="http://your-server:3006"
|
||||||
export CLI_API_TOKEN="your-token-here"
|
export CLI_API_TOKEN="your-token-here"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -154,7 +156,7 @@ If you prefer not to use the public relay (e.g., for lower latency or self-manag
|
|||||||
https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/
|
https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export WEBAPP_URL="https://your-tunnel.trycloudflare.com"
|
export HAPI_PUBLIC_URL="https://your-tunnel.trycloudflare.com"
|
||||||
hapi server
|
hapi server
|
||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
@@ -194,7 +196,7 @@ Enable Telegram notifications and Mini App access:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
export TELEGRAM_BOT_TOKEN="your-bot-token"
|
export TELEGRAM_BOT_TOKEN="your-bot-token"
|
||||||
export WEBAPP_URL="https://your-public-url"
|
export HAPI_PUBLIC_URL="https://your-public-url"
|
||||||
|
|
||||||
hapi server
|
hapi server
|
||||||
```
|
```
|
||||||
|
|||||||
+7
-6
@@ -22,11 +22,12 @@ See `src/configuration.ts` for all options.
|
|||||||
### Optional (Telegram)
|
### Optional (Telegram)
|
||||||
|
|
||||||
- `TELEGRAM_BOT_TOKEN` - Token from @BotFather.
|
- `TELEGRAM_BOT_TOKEN` - Token from @BotFather.
|
||||||
- `WEBAPP_URL` - Public HTTPS URL for Telegram Mini App access. Also used to derive default CORS origins for the web app.
|
- `HAPI_PUBLIC_URL` - Public HTTPS URL for Telegram Mini App access. Also used to derive default CORS origins for the web app.
|
||||||
|
|
||||||
### Optional
|
### Optional
|
||||||
|
|
||||||
- `WEBAPP_PORT` - HTTP port (default: 3006).
|
- `HAPI_LISTEN_HOST` - HTTP bind address (default: 127.0.0.1).
|
||||||
|
- `HAPI_LISTEN_PORT` - HTTP port (default: 3006).
|
||||||
- `CORS_ORIGINS` - Comma-separated origins, or `*`.
|
- `CORS_ORIGINS` - Comma-separated origins, or `*`.
|
||||||
- `HAPI_HOME` - Data directory (default: ~/.hapi).
|
- `HAPI_HOME` - Data directory (default: ~/.hapi).
|
||||||
- `DB_PATH` - SQLite database path (default: HAPI_HOME/hapi.db).
|
- `DB_PATH` - SQLite database path (default: HAPI_HOME/hapi.db).
|
||||||
@@ -38,13 +39,13 @@ Binary (single executable):
|
|||||||
```bash
|
```bash
|
||||||
export TELEGRAM_BOT_TOKEN="..."
|
export TELEGRAM_BOT_TOKEN="..."
|
||||||
export CLI_API_TOKEN="shared-secret"
|
export CLI_API_TOKEN="shared-secret"
|
||||||
export WEBAPP_URL="https://your-domain.example"
|
export HAPI_PUBLIC_URL="https://your-domain.example"
|
||||||
|
|
||||||
hapi server
|
hapi server
|
||||||
```
|
```
|
||||||
|
|
||||||
If you only need web + CLI, you can omit TELEGRAM_BOT_TOKEN.
|
If you only need web + CLI, you can omit TELEGRAM_BOT_TOKEN.
|
||||||
To enable Telegram, set TELEGRAM_BOT_TOKEN and WEBAPP_URL, start the server, open `/app`
|
To enable Telegram, set TELEGRAM_BOT_TOKEN and HAPI_PUBLIC_URL, start the server, open `/app`
|
||||||
in the bot chat, and bind the Mini App with `CLI_API_TOKEN:<namespace>` when prompted.
|
in the bot chat, and bind the Mini App with `CLI_API_TOKEN:<namespace>` when prompted.
|
||||||
|
|
||||||
From source:
|
From source:
|
||||||
@@ -200,14 +201,14 @@ The server build output is `server/dist/index.js`, and the web assets are in `we
|
|||||||
## Networking notes
|
## Networking notes
|
||||||
|
|
||||||
- Telegram Mini Apps require HTTPS and a public URL. If the server has no public IP, use Cloudflare Tunnel or Tailscale and set `WEBAPP_URL` to the HTTPS endpoint.
|
- Telegram Mini Apps require HTTPS and a public URL. If the server has no public IP, use Cloudflare Tunnel or Tailscale and set `WEBAPP_URL` to the HTTPS endpoint.
|
||||||
- If the web app is hosted on a different origin, set `CORS_ORIGINS` (or `WEBAPP_URL`) to include that static host origin.
|
- If the web app is hosted on a different origin, set `CORS_ORIGINS` (or `HAPI_PUBLIC_URL`) to include that static host origin.
|
||||||
|
|
||||||
## Standalone web hosting
|
## Standalone web hosting
|
||||||
|
|
||||||
The web UI can be hosted separately from the server (for example on GitHub Pages or Cloudflare Pages):
|
The web UI can be hosted separately from the server (for example on GitHub Pages or Cloudflare Pages):
|
||||||
|
|
||||||
1. Build and deploy `web/dist` from the repo root.
|
1. Build and deploy `web/dist` from the repo root.
|
||||||
2. Set `CORS_ORIGINS` (or `WEBAPP_URL`) to the static host origin.
|
2. Set `CORS_ORIGINS` (or `HAPI_PUBLIC_URL`) to the static host origin.
|
||||||
3. Open the static site, click the Server button on the login screen, and enter the hapi server origin.
|
3. Open the static site, click the Server button on the login screen, and enter the hapi server origin.
|
||||||
|
|
||||||
Leaving the server override empty preserves the default same-origin behavior when the server serves the web assets directly.
|
Leaving the server override empty preserves the default same-origin behavior when the server serves the web assets directly.
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import { getSettingsFile, readSettings, writeSettings } from './settings'
|
|||||||
export interface ServerSettings {
|
export interface ServerSettings {
|
||||||
telegramBotToken: string | null
|
telegramBotToken: string | null
|
||||||
telegramNotification: boolean
|
telegramNotification: boolean
|
||||||
webappHost: string
|
listenHost: string
|
||||||
webappPort: number
|
listenPort: number
|
||||||
webappUrl: string
|
publicUrl: string
|
||||||
corsOrigins: string[]
|
corsOrigins: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,9 +24,9 @@ export interface ServerSettingsResult {
|
|||||||
sources: {
|
sources: {
|
||||||
telegramBotToken: 'env' | 'file' | 'default'
|
telegramBotToken: 'env' | 'file' | 'default'
|
||||||
telegramNotification: 'env' | 'file' | 'default'
|
telegramNotification: 'env' | 'file' | 'default'
|
||||||
webappHost: 'env' | 'file' | 'default'
|
listenHost: 'env' | 'file' | 'default'
|
||||||
webappPort: 'env' | 'file' | 'default'
|
listenPort: 'env' | 'file' | 'default'
|
||||||
webappUrl: 'env' | 'file' | 'default'
|
publicUrl: 'env' | 'file' | 'default'
|
||||||
corsOrigins: 'env' | 'file' | 'default'
|
corsOrigins: 'env' | 'file' | 'default'
|
||||||
}
|
}
|
||||||
savedToFile: boolean
|
savedToFile: boolean
|
||||||
@@ -58,11 +58,11 @@ function parseCorsOrigins(str: string): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derive CORS origins from webapp URL
|
* Derive CORS origins from public URL
|
||||||
*/
|
*/
|
||||||
function deriveCorsOrigins(webappUrl: string): string[] {
|
function deriveCorsOrigins(publicUrl: string): string[] {
|
||||||
try {
|
try {
|
||||||
return [new URL(webappUrl).origin]
|
return [new URL(publicUrl).origin]
|
||||||
} catch {
|
} catch {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@@ -87,9 +87,9 @@ export async function loadServerSettings(dataDir: string): Promise<ServerSetting
|
|||||||
const sources: ServerSettingsResult['sources'] = {
|
const sources: ServerSettingsResult['sources'] = {
|
||||||
telegramBotToken: 'default',
|
telegramBotToken: 'default',
|
||||||
telegramNotification: 'default',
|
telegramNotification: 'default',
|
||||||
webappHost: 'default',
|
listenHost: 'default',
|
||||||
webappPort: 'default',
|
listenPort: 'default',
|
||||||
webappUrl: 'default',
|
publicUrl: 'default',
|
||||||
corsOrigins: 'default',
|
corsOrigins: 'default',
|
||||||
}
|
}
|
||||||
// telegramBotToken: env > file > null
|
// telegramBotToken: env > file > null
|
||||||
@@ -120,53 +120,74 @@ export async function loadServerSettings(dataDir: string): Promise<ServerSetting
|
|||||||
sources.telegramNotification = 'file'
|
sources.telegramNotification = 'file'
|
||||||
}
|
}
|
||||||
|
|
||||||
// webappHost: env > file > 127.0.0.1
|
// listenHost: env > file (new or old name) > default
|
||||||
let webappHost = '127.0.0.1'
|
let listenHost = '127.0.0.1'
|
||||||
if (process.env.WEBAPP_HOST) {
|
if (process.env.HAPI_LISTEN_HOST) {
|
||||||
webappHost = process.env.WEBAPP_HOST
|
listenHost = process.env.HAPI_LISTEN_HOST
|
||||||
sources.webappHost = 'env'
|
sources.listenHost = 'env'
|
||||||
if (settings.webappHost === undefined) {
|
if (settings.listenHost === undefined) {
|
||||||
settings.webappHost = webappHost
|
settings.listenHost = listenHost
|
||||||
needsSave = true
|
needsSave = true
|
||||||
}
|
}
|
||||||
|
} else if (settings.listenHost !== undefined) {
|
||||||
|
listenHost = settings.listenHost
|
||||||
|
sources.listenHost = 'file'
|
||||||
} else if (settings.webappHost !== undefined) {
|
} else if (settings.webappHost !== undefined) {
|
||||||
webappHost = settings.webappHost
|
// Migrate from old field name
|
||||||
sources.webappHost = 'file'
|
listenHost = settings.webappHost
|
||||||
|
sources.listenHost = 'file'
|
||||||
|
settings.listenHost = listenHost
|
||||||
|
delete settings.webappHost
|
||||||
|
needsSave = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// webappPort: env > file > 3006
|
// listenPort: env > file (new or old name) > default
|
||||||
let webappPort = 3006
|
let listenPort = 3006
|
||||||
if (process.env.WEBAPP_PORT) {
|
if (process.env.HAPI_LISTEN_PORT) {
|
||||||
const parsed = parseInt(process.env.WEBAPP_PORT, 10)
|
const parsed = parseInt(process.env.HAPI_LISTEN_PORT, 10)
|
||||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
||||||
throw new Error('WEBAPP_PORT must be a valid port number')
|
throw new Error('HAPI_LISTEN_PORT must be a valid port number')
|
||||||
}
|
}
|
||||||
webappPort = parsed
|
listenPort = parsed
|
||||||
sources.webappPort = 'env'
|
sources.listenPort = 'env'
|
||||||
if (settings.webappPort === undefined) {
|
if (settings.listenPort === undefined) {
|
||||||
settings.webappPort = webappPort
|
settings.listenPort = listenPort
|
||||||
needsSave = true
|
needsSave = true
|
||||||
}
|
}
|
||||||
|
} else if (settings.listenPort !== undefined) {
|
||||||
|
listenPort = settings.listenPort
|
||||||
|
sources.listenPort = 'file'
|
||||||
} else if (settings.webappPort !== undefined) {
|
} else if (settings.webappPort !== undefined) {
|
||||||
webappPort = settings.webappPort
|
// Migrate from old field name
|
||||||
sources.webappPort = 'file'
|
listenPort = settings.webappPort
|
||||||
|
sources.listenPort = 'file'
|
||||||
|
settings.listenPort = listenPort
|
||||||
|
delete settings.webappPort
|
||||||
|
needsSave = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// webappUrl: env > file > http://localhost:{port}
|
// publicUrl: env > file (new or old name) > default
|
||||||
let webappUrl = `http://localhost:${webappPort}`
|
let publicUrl = `http://localhost:${listenPort}`
|
||||||
if (process.env.WEBAPP_URL) {
|
if (process.env.HAPI_PUBLIC_URL) {
|
||||||
webappUrl = process.env.WEBAPP_URL
|
publicUrl = process.env.HAPI_PUBLIC_URL
|
||||||
sources.webappUrl = 'env'
|
sources.publicUrl = 'env'
|
||||||
if (settings.webappUrl === undefined) {
|
if (settings.publicUrl === undefined) {
|
||||||
settings.webappUrl = webappUrl
|
settings.publicUrl = publicUrl
|
||||||
needsSave = true
|
needsSave = true
|
||||||
}
|
}
|
||||||
|
} else if (settings.publicUrl !== undefined) {
|
||||||
|
publicUrl = settings.publicUrl
|
||||||
|
sources.publicUrl = 'file'
|
||||||
} else if (settings.webappUrl !== undefined) {
|
} else if (settings.webappUrl !== undefined) {
|
||||||
webappUrl = settings.webappUrl
|
// Migrate from old field name
|
||||||
sources.webappUrl = 'file'
|
publicUrl = settings.webappUrl
|
||||||
|
sources.publicUrl = 'file'
|
||||||
|
settings.publicUrl = publicUrl
|
||||||
|
delete settings.webappUrl
|
||||||
|
needsSave = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// corsOrigins: env > file > derived from webappUrl
|
// corsOrigins: env > file > derived from publicUrl
|
||||||
let corsOrigins: string[]
|
let corsOrigins: string[]
|
||||||
if (process.env.CORS_ORIGINS) {
|
if (process.env.CORS_ORIGINS) {
|
||||||
corsOrigins = parseCorsOrigins(process.env.CORS_ORIGINS)
|
corsOrigins = parseCorsOrigins(process.env.CORS_ORIGINS)
|
||||||
@@ -179,7 +200,7 @@ export async function loadServerSettings(dataDir: string): Promise<ServerSetting
|
|||||||
corsOrigins = settings.corsOrigins
|
corsOrigins = settings.corsOrigins
|
||||||
sources.corsOrigins = 'file'
|
sources.corsOrigins = 'file'
|
||||||
} else {
|
} else {
|
||||||
corsOrigins = deriveCorsOrigins(webappUrl)
|
corsOrigins = deriveCorsOrigins(publicUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save settings if any new values were added
|
// Save settings if any new values were added
|
||||||
@@ -191,9 +212,9 @@ export async function loadServerSettings(dataDir: string): Promise<ServerSetting
|
|||||||
settings: {
|
settings: {
|
||||||
telegramBotToken,
|
telegramBotToken,
|
||||||
telegramNotification,
|
telegramNotification,
|
||||||
webappHost,
|
listenHost,
|
||||||
webappPort,
|
listenPort,
|
||||||
webappUrl,
|
publicUrl,
|
||||||
corsOrigins,
|
corsOrigins,
|
||||||
},
|
},
|
||||||
sources,
|
sources,
|
||||||
|
|||||||
@@ -14,10 +14,14 @@ export interface Settings {
|
|||||||
// Server configuration (persisted from environment variables)
|
// Server configuration (persisted from environment variables)
|
||||||
telegramBotToken?: string
|
telegramBotToken?: string
|
||||||
telegramNotification?: boolean
|
telegramNotification?: boolean
|
||||||
|
listenHost?: string
|
||||||
|
listenPort?: number
|
||||||
|
publicUrl?: string
|
||||||
|
corsOrigins?: string[]
|
||||||
|
// Legacy field names (for migration, read-only)
|
||||||
webappHost?: string
|
webappHost?: string
|
||||||
webappPort?: number
|
webappPort?: number
|
||||||
webappUrl?: string
|
webappUrl?: string
|
||||||
corsOrigins?: string[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSettingsFile(dataDir: string): string {
|
export function getSettingsFile(dataDir: string): string {
|
||||||
|
|||||||
+15
-14
@@ -9,8 +9,9 @@
|
|||||||
* - CLI_API_TOKEN: Shared secret for hapi CLI authentication (auto-generated if not set)
|
* - CLI_API_TOKEN: Shared secret for hapi CLI authentication (auto-generated if not set)
|
||||||
* - TELEGRAM_BOT_TOKEN: Telegram Bot API token from @BotFather
|
* - TELEGRAM_BOT_TOKEN: Telegram Bot API token from @BotFather
|
||||||
* - TELEGRAM_NOTIFICATION: Enable/disable Telegram notifications (default: true)
|
* - TELEGRAM_NOTIFICATION: Enable/disable Telegram notifications (default: true)
|
||||||
* - WEBAPP_PORT: Port for Mini App HTTP server (default: 3006)
|
* - HAPI_LISTEN_HOST: Host/IP to bind the HTTP server (default: 127.0.0.1)
|
||||||
* - WEBAPP_URL: Public URL for Telegram Mini App
|
* - HAPI_LISTEN_PORT: Port for HTTP server (default: 3006)
|
||||||
|
* - HAPI_PUBLIC_URL: Public URL for external access (e.g., Telegram Mini App)
|
||||||
* - CORS_ORIGINS: Comma-separated CORS origins
|
* - CORS_ORIGINS: Comma-separated CORS origins
|
||||||
* - HAPI_RELAY_API: Relay API domain for tunwg (default: relay.hapi.run)
|
* - HAPI_RELAY_API: Relay API domain for tunwg (default: relay.hapi.run)
|
||||||
* - HAPI_RELAY_AUTH: Relay auth key for tunwg (default: hapi)
|
* - HAPI_RELAY_AUTH: Relay auth key for tunwg (default: hapi)
|
||||||
@@ -32,9 +33,9 @@ export type ConfigSource = 'env' | 'file' | 'default'
|
|||||||
export interface ConfigSources {
|
export interface ConfigSources {
|
||||||
telegramBotToken: ConfigSource
|
telegramBotToken: ConfigSource
|
||||||
telegramNotification: ConfigSource
|
telegramNotification: ConfigSource
|
||||||
webappHost: ConfigSource
|
listenHost: ConfigSource
|
||||||
webappPort: ConfigSource
|
listenPort: ConfigSource
|
||||||
webappUrl: ConfigSource
|
publicUrl: ConfigSource
|
||||||
corsOrigins: ConfigSource
|
corsOrigins: ConfigSource
|
||||||
cliApiToken: 'env' | 'file' | 'generated'
|
cliApiToken: 'env' | 'file' | 'generated'
|
||||||
}
|
}
|
||||||
@@ -67,14 +68,14 @@ class Configuration {
|
|||||||
/** SQLite DB path */
|
/** SQLite DB path */
|
||||||
public readonly dbPath: string
|
public readonly dbPath: string
|
||||||
|
|
||||||
/** Port for the Mini App HTTP server */
|
/** Port for the HTTP server */
|
||||||
public readonly webappPort: number
|
public readonly listenPort: number
|
||||||
|
|
||||||
/** Host/IP to bind the Mini App HTTP server to */
|
/** Host/IP to bind the HTTP server to */
|
||||||
public readonly webappHost: string
|
public readonly listenHost: string
|
||||||
|
|
||||||
/** Public HTTPS URL for the Telegram Mini App (used in WebApp buttons) */
|
/** Public URL for external access (e.g., Telegram Mini App) */
|
||||||
public readonly miniAppUrl: string
|
public readonly publicUrl: string
|
||||||
|
|
||||||
/** Allowed CORS origins for Mini App + Socket.IO (comma-separated env override) */
|
/** Allowed CORS origins for Mini App + Socket.IO (comma-separated env override) */
|
||||||
public readonly corsOrigins: string[]
|
public readonly corsOrigins: string[]
|
||||||
@@ -97,9 +98,9 @@ class Configuration {
|
|||||||
this.telegramBotToken = serverSettings.telegramBotToken
|
this.telegramBotToken = serverSettings.telegramBotToken
|
||||||
this.telegramEnabled = Boolean(this.telegramBotToken)
|
this.telegramEnabled = Boolean(this.telegramBotToken)
|
||||||
this.telegramNotification = serverSettings.telegramNotification
|
this.telegramNotification = serverSettings.telegramNotification
|
||||||
this.webappHost = serverSettings.webappHost
|
this.listenHost = serverSettings.listenHost
|
||||||
this.webappPort = serverSettings.webappPort
|
this.listenPort = serverSettings.listenPort
|
||||||
this.miniAppUrl = serverSettings.webappUrl
|
this.publicUrl = serverSettings.publicUrl
|
||||||
this.corsOrigins = serverSettings.corsOrigins
|
this.corsOrigins = serverSettings.corsOrigins
|
||||||
|
|
||||||
// CLI API token - will be set by _setCliApiToken() before create() returns
|
// CLI API token - will be set by _setCliApiToken() before create() returns
|
||||||
|
|||||||
+8
-8
@@ -137,9 +137,9 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Display other configuration sources
|
// Display other configuration sources
|
||||||
console.log(`[Server] WEBAPP_HOST: ${config.webappHost} (${formatSource(config.sources.webappHost)})`)
|
console.log(`[Server] HAPI_LISTEN_HOST: ${config.listenHost} (${formatSource(config.sources.listenHost)})`)
|
||||||
console.log(`[Server] WEBAPP_PORT: ${config.webappPort} (${formatSource(config.sources.webappPort)})`)
|
console.log(`[Server] HAPI_LISTEN_PORT: ${config.listenPort} (${formatSource(config.sources.listenPort)})`)
|
||||||
console.log(`[Server] WEBAPP_URL: ${config.miniAppUrl} (${formatSource(config.sources.webappUrl)})`)
|
console.log(`[Server] HAPI_PUBLIC_URL: ${config.publicUrl} (${formatSource(config.sources.publicUrl)})`)
|
||||||
|
|
||||||
if (!config.telegramEnabled) {
|
if (!config.telegramEnabled) {
|
||||||
console.log('[Server] Telegram: disabled (no TELEGRAM_BOT_TOKEN)')
|
console.log('[Server] Telegram: disabled (no TELEGRAM_BOT_TOKEN)')
|
||||||
@@ -180,7 +180,7 @@ async function main() {
|
|||||||
syncEngine = new SyncEngine(store, socketServer.io, socketServer.rpcRegistry, sseManager)
|
syncEngine = new SyncEngine(store, socketServer.io, socketServer.rpcRegistry, sseManager)
|
||||||
|
|
||||||
const notificationChannels: NotificationChannel[] = [
|
const notificationChannels: NotificationChannel[] = [
|
||||||
new PushNotificationChannel(pushService, sseManager, visibilityTracker, config.miniAppUrl)
|
new PushNotificationChannel(pushService, sseManager, visibilityTracker, config.publicUrl)
|
||||||
]
|
]
|
||||||
|
|
||||||
// Initialize Telegram bot (optional)
|
// Initialize Telegram bot (optional)
|
||||||
@@ -188,7 +188,7 @@ async function main() {
|
|||||||
happyBot = new HappyBot({
|
happyBot = new HappyBot({
|
||||||
syncEngine,
|
syncEngine,
|
||||||
botToken: config.telegramBotToken,
|
botToken: config.telegramBotToken,
|
||||||
miniAppUrl: config.miniAppUrl,
|
publicUrl: config.publicUrl,
|
||||||
store
|
store
|
||||||
})
|
})
|
||||||
// Only add to notification channels if notifications are enabled
|
// Only add to notification channels if notifications are enabled
|
||||||
@@ -219,14 +219,14 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('')
|
console.log('')
|
||||||
console.log('[Web] Server listening on :' + config.webappPort)
|
console.log('[Web] Server listening on :' + config.listenPort)
|
||||||
console.log('[Web] Local: http://localhost:' + config.webappPort)
|
console.log('[Web] Local: http://localhost:' + config.listenPort)
|
||||||
|
|
||||||
// Initialize tunnel AFTER web server is ready
|
// Initialize tunnel AFTER web server is ready
|
||||||
let tunnelUrl: string | null = null
|
let tunnelUrl: string | null = null
|
||||||
if (relayFlag.enabled) {
|
if (relayFlag.enabled) {
|
||||||
tunnelManager = new TunnelManager({
|
tunnelManager = new TunnelManager({
|
||||||
localPort: config.webappPort,
|
localPort: config.listenPort,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
apiDomain: relayApiDomain,
|
apiDomain: relayApiDomain,
|
||||||
authKey: process.env.HAPI_RELAY_AUTH || null,
|
authKey: process.env.HAPI_RELAY_AUTH || null,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export interface BotContext extends Context {
|
|||||||
export interface HappyBotConfig {
|
export interface HappyBotConfig {
|
||||||
syncEngine: SyncEngine
|
syncEngine: SyncEngine
|
||||||
botToken: string
|
botToken: string
|
||||||
miniAppUrl: string
|
publicUrl: string
|
||||||
store: Store
|
store: Store
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,12 +31,12 @@ export class HappyBot implements NotificationChannel {
|
|||||||
private bot: Bot<BotContext>
|
private bot: Bot<BotContext>
|
||||||
private syncEngine: SyncEngine | null = null
|
private syncEngine: SyncEngine | null = null
|
||||||
private isRunning = false
|
private isRunning = false
|
||||||
private readonly miniAppUrl: string
|
private readonly publicUrl: string
|
||||||
private readonly store: Store
|
private readonly store: Store
|
||||||
|
|
||||||
constructor(config: HappyBotConfig) {
|
constructor(config: HappyBotConfig) {
|
||||||
this.syncEngine = config.syncEngine
|
this.syncEngine = config.syncEngine
|
||||||
this.miniAppUrl = config.miniAppUrl
|
this.publicUrl = config.publicUrl
|
||||||
this.store = config.store
|
this.store = config.store
|
||||||
|
|
||||||
this.bot = new Bot<BotContext>(config.botToken)
|
this.bot = new Bot<BotContext>(config.botToken)
|
||||||
@@ -108,13 +108,13 @@ export class HappyBot implements NotificationChannel {
|
|||||||
private setupCommands(): void {
|
private setupCommands(): void {
|
||||||
// /app - Open Telegram Mini App (primary entry point)
|
// /app - Open Telegram Mini App (primary entry point)
|
||||||
this.bot.command('app', async (ctx) => {
|
this.bot.command('app', async (ctx) => {
|
||||||
const keyboard = new InlineKeyboard().webApp('Open App', this.miniAppUrl)
|
const keyboard = new InlineKeyboard().webApp('Open App', this.publicUrl)
|
||||||
await ctx.reply('Open HAPI Mini App:', { reply_markup: keyboard })
|
await ctx.reply('Open HAPI Mini App:', { reply_markup: keyboard })
|
||||||
})
|
})
|
||||||
|
|
||||||
// /start - Simple welcome with Mini App link
|
// /start - Simple welcome with Mini App link
|
||||||
this.bot.command('start', async (ctx) => {
|
this.bot.command('start', async (ctx) => {
|
||||||
const keyboard = new InlineKeyboard().webApp('Open App', this.miniAppUrl)
|
const keyboard = new InlineKeyboard().webApp('Open App', this.publicUrl)
|
||||||
await ctx.reply(
|
await ctx.reply(
|
||||||
'Welcome to HAPI Bot!\n\n' +
|
'Welcome to HAPI Bot!\n\n' +
|
||||||
'Use the Mini App for full session management.',
|
'Use the Mini App for full session management.',
|
||||||
@@ -190,7 +190,7 @@ export class HappyBot implements NotificationChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const agentName = getAgentName(session)
|
const agentName = getAgentName(session)
|
||||||
const url = buildMiniAppDeepLink(this.miniAppUrl, `session_${session.id}`)
|
const url = buildMiniAppDeepLink(this.publicUrl, `session_${session.id}`)
|
||||||
const keyboard = new InlineKeyboard()
|
const keyboard = new InlineKeyboard()
|
||||||
.webApp('Open Session', url)
|
.webApp('Open Session', url)
|
||||||
|
|
||||||
@@ -221,7 +221,7 @@ export class HappyBot implements NotificationChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const text = formatSessionNotification(session)
|
const text = formatSessionNotification(session)
|
||||||
const keyboard = createNotificationKeyboard(session, this.miniAppUrl)
|
const keyboard = createNotificationKeyboard(session, this.publicUrl)
|
||||||
|
|
||||||
const chatIds = this.getBoundChatIds(session.namespace)
|
const chatIds = this.getBoundChatIds(session.namespace)
|
||||||
if (chatIds.length === 0) {
|
if (chatIds.length === 0) {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export function formatSessionNotification(session: Session): string {
|
|||||||
/**
|
/**
|
||||||
* Create notification keyboard for quick actions
|
* Create notification keyboard for quick actions
|
||||||
*/
|
*/
|
||||||
export function createNotificationKeyboard(session: Session, miniAppUrl: string): InlineKeyboard {
|
export function createNotificationKeyboard(session: Session, publicUrl: string): InlineKeyboard {
|
||||||
const keyboard = new InlineKeyboard()
|
const keyboard = new InlineKeyboard()
|
||||||
const requests = session.agentState?.requests ?? null
|
const requests = session.agentState?.requests ?? null
|
||||||
const hasRequests = Boolean(requests && Object.keys(requests).length > 0)
|
const hasRequests = Boolean(requests && Object.keys(requests).length > 0)
|
||||||
@@ -55,14 +55,14 @@ export function createNotificationKeyboard(session: Session, miniAppUrl: string)
|
|||||||
|
|
||||||
keyboard.webApp(
|
keyboard.webApp(
|
||||||
'Details',
|
'Details',
|
||||||
buildMiniAppDeepLink(miniAppUrl, `session_${session.id}`)
|
buildMiniAppDeepLink(publicUrl, `session_${session.id}`)
|
||||||
)
|
)
|
||||||
return keyboard
|
return keyboard
|
||||||
}
|
}
|
||||||
|
|
||||||
keyboard.webApp(
|
keyboard.webApp(
|
||||||
'Open Session',
|
'Open Session',
|
||||||
buildMiniAppDeepLink(miniAppUrl, `session_${session.id}`)
|
buildMiniAppDeepLink(publicUrl, `session_${session.id}`)
|
||||||
)
|
)
|
||||||
return keyboard
|
return keyboard
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,8 +228,8 @@ export async function startWebServer(options: {
|
|||||||
const socketHandler = options.socketEngine.handler()
|
const socketHandler = options.socketEngine.handler()
|
||||||
|
|
||||||
const server = Bun.serve({
|
const server = Bun.serve({
|
||||||
hostname: configuration.webappHost,
|
hostname: configuration.listenHost,
|
||||||
port: configuration.webappPort,
|
port: configuration.listenPort,
|
||||||
idleTimeout: Math.max(30, socketHandler.idleTimeout),
|
idleTimeout: Math.max(30, socketHandler.idleTimeout),
|
||||||
maxRequestBodySize: socketHandler.maxRequestBodySize,
|
maxRequestBodySize: socketHandler.maxRequestBodySize,
|
||||||
websocket: socketHandler.websocket,
|
websocket: socketHandler.websocket,
|
||||||
@@ -242,8 +242,8 @@ export async function startWebServer(options: {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(`[Web] Mini App server listening on ${configuration.webappHost}:${configuration.webappPort}`)
|
console.log(`[Web] server listening on ${configuration.listenHost}:${configuration.listenPort}`)
|
||||||
console.log(`[Web] Mini App public URL: ${configuration.miniAppUrl}`)
|
console.log(`[Web] public URL: ${configuration.publicUrl}`)
|
||||||
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user