mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat: rename server package to hub
Rename the `server/` directory to `hub/` and update all references across CLI, docs, web, and workspace configuration.
This commit is contained in:
+7
-7
@@ -2,10 +2,10 @@
|
||||
|
||||
## Project Overview
|
||||
|
||||
HAPI CLI (`hapi`) is a command-line tool that wraps Claude Code to enable remote control and session sharing via `hapi-server` (Telegram Bot + Mini App). It's part of a two-component system:
|
||||
HAPI CLI (`hapi`) is a command-line tool that wraps Claude Code to enable remote control and session sharing via `hapi-hub` (Telegram Bot + Mini App). It's part of a two-component system:
|
||||
|
||||
1. **hapi** (this project) - CLI wrapper for Claude Code
|
||||
2. **hapi-server** - Public server (Socket.IO + REST + SQLite) + Telegram Mini App
|
||||
2. **hapi-hub** - Hub service (Socket.IO + REST + SQLite) + Telegram Mini App
|
||||
|
||||
## Code Style Preferences
|
||||
|
||||
@@ -96,29 +96,29 @@ User interface components.
|
||||
## Data Flow
|
||||
|
||||
1. **Authentication**:
|
||||
- Use `CLI_API_TOKEN` to authenticate to `hapi-server` (REST + Socket.IO)
|
||||
- Use `CLI_API_TOKEN` to authenticate to `hapi-hub` (REST + Socket.IO)
|
||||
|
||||
2. **Session Creation**:
|
||||
- Create/load session via `POST /cli/sessions` → Establish Socket.IO `/cli` connection
|
||||
|
||||
3. **Message Flow**:
|
||||
- Local mode: terminal/SDK → hapi CLI → hapi-server → Telegram Mini App
|
||||
- Local mode: terminal/SDK → hapi CLI → hapi-hub → Telegram Mini App
|
||||
|
||||
4. **Permission Handling**:
|
||||
- Claude requests permission → hapi CLI exposes RPC handlers → Mini App calls REST → hapi-server relays RPC to hapi CLI
|
||||
- Claude requests permission → hapi CLI exposes RPC handlers → Mini App calls REST → hapi-hub relays RPC to hapi CLI
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
1. **File-based logging**: Prevents interference with Claude's terminal UI
|
||||
2. **Dual Claude integration**: Process spawning for interactive, SDK for remote
|
||||
3. **No E2E encryption**: Use HTTPS/TLS for `hapi-server` deployments
|
||||
3. **No E2E encryption**: Use HTTPS/TLS for `hapi-hub` deployments
|
||||
4. **Session persistence**: Allows resuming sessions across restarts
|
||||
5. **Optimistic concurrency**: Handles distributed state updates gracefully
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- `CLI_API_TOKEN` is a shared secret; treat it like a password.
|
||||
- No end-to-end encryption: use HTTPS/TLS for `hapi-server` deployments.
|
||||
- No end-to-end encryption: use HTTPS/TLS for `hapi-hub` deployments.
|
||||
- Session isolation through unique session IDs.
|
||||
|
||||
## Dependencies
|
||||
|
||||
+9
-8
@@ -1,10 +1,10 @@
|
||||
# hapi CLI
|
||||
|
||||
Run Claude Code, Codex, or Gemini sessions from your terminal and control them remotely through the hapi server.
|
||||
Run Claude Code, Codex, or Gemini sessions from your terminal and control them remotely through the hapi hub.
|
||||
|
||||
## What it does
|
||||
|
||||
- Starts Claude Code sessions and registers them with hapi-server.
|
||||
- Starts Claude Code sessions and registers them with hapi-hub.
|
||||
- Starts Codex mode for OpenAI-based sessions.
|
||||
- Starts Gemini mode via ACP (Anthropic Code Plugins).
|
||||
- Provides an MCP stdio bridge for external tools.
|
||||
@@ -13,7 +13,7 @@ Run Claude Code, Codex, or Gemini sessions from your terminal and control them r
|
||||
|
||||
## Typical flow
|
||||
|
||||
1. Start the server and set env vars (see ../server/README.md).
|
||||
1. Start the hub and set env vars (see ../hub/README.md).
|
||||
2. Set the same CLI_API_TOKEN on this machine or run `hapi auth login`.
|
||||
3. Run `hapi` to start a session.
|
||||
4. Use the web app or Telegram Mini App to monitor and control.
|
||||
@@ -25,7 +25,7 @@ Run Claude Code, Codex, or Gemini sessions from your terminal and control them r
|
||||
- `hapi` - Start a Claude Code session (passes through Claude CLI flags). See `src/index.ts`.
|
||||
- `hapi codex` - Start Codex mode. See `src/codex/runCodex.ts`.
|
||||
- `hapi gemini` - Start Gemini mode via ACP. See `src/agent/runners/runAgentSession.ts`.
|
||||
Note: Gemini runs in remote mode only; it waits for messages from the server UI/Telegram.
|
||||
Note: Gemini runs in remote mode only; it waits for messages from the hub UI/Telegram.
|
||||
|
||||
### Authentication
|
||||
|
||||
@@ -58,7 +58,8 @@ See `src/ui/doctor.ts`.
|
||||
### Other
|
||||
|
||||
- `hapi mcp` - Start MCP stdio bridge. See `src/codex/happyMcpStdioBridge.ts`.
|
||||
- `hapi server` - Start the bundled server (single binary workflow).
|
||||
- `hapi hub` - Start the bundled hub (single binary workflow).
|
||||
- `hapi server` - Alias for `hapi hub`.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -66,8 +67,8 @@ See `src/configuration.ts` for all options.
|
||||
|
||||
### Required
|
||||
|
||||
- `CLI_API_TOKEN` - Shared secret; must match the server. Can be set via env or `~/.hapi/settings.json` (env wins).
|
||||
- `HAPI_API_URL` - Server base URL (default: http://localhost:3006).
|
||||
- `CLI_API_TOKEN` - Shared secret; must match the hub. Can be set via env or `~/.hapi/settings.json` (env wins).
|
||||
- `HAPI_API_URL` - Hub base URL (default: http://localhost:3006).
|
||||
|
||||
### Optional
|
||||
|
||||
@@ -123,5 +124,5 @@ bun run build:single-exe
|
||||
|
||||
## Related docs
|
||||
|
||||
- `../server/README.md`
|
||||
- `../hub/README.md`
|
||||
- `../web/README.md`
|
||||
|
||||
@@ -126,7 +126,7 @@ function resolveOutdir(projectRoot: string, outdir: string): string {
|
||||
}
|
||||
|
||||
function writeStubEmbeddedAssets(workspaceRoot: string): void {
|
||||
const outputPath = join(workspaceRoot, 'server', 'src', 'web', 'embeddedAssets.generated.ts');
|
||||
const outputPath = join(workspaceRoot, 'hub', 'src', 'web', 'embeddedAssets.generated.ts');
|
||||
const contents = [
|
||||
'// This file is generated by cli/scripts/build-executable.ts when --with-web-assets is not used.',
|
||||
'// It intentionally contains no embedded assets.',
|
||||
@@ -153,16 +153,16 @@ function isStubEmbeddedAssets(manifestPath: string): boolean {
|
||||
}
|
||||
|
||||
function ensureEmbeddedAssetsManifest(workspaceRoot: string, includeWebAssets: boolean): void {
|
||||
const manifestPath = join(workspaceRoot, 'server', 'src', 'web', 'embeddedAssets.generated.ts');
|
||||
const manifestPath = join(workspaceRoot, 'hub', 'src', 'web', 'embeddedAssets.generated.ts');
|
||||
if (includeWebAssets) {
|
||||
if (!existsSync(manifestPath)) {
|
||||
throw new Error(
|
||||
'Missing embedded web asset manifest. Run `bun run build:web` and `cd server && bun run generate:embedded-web-assets`, or `bun run build:single-exe` from the repo root.'
|
||||
'Missing embedded web asset manifest. Run `bun run build:web` and `cd hub && bun run generate:embedded-web-assets`, or `bun run build:single-exe` from the repo root.'
|
||||
);
|
||||
}
|
||||
if (isStubEmbeddedAssets(manifestPath)) {
|
||||
throw new Error(
|
||||
'Embedded web asset manifest is a stub. Run `bun run build:web` and `cd server && bun run generate:embedded-web-assets`, or `bun run build:single-exe` from the repo root.'
|
||||
'Embedded web asset manifest is a stub. Run `bun run build:web` and `cd hub && bun run generate:embedded-web-assets`, or `bun run build:single-exe` from the repo root.'
|
||||
);
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'node:url';
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const cliRoot = join(scriptDir, '..');
|
||||
const workspaceRoot = join(cliRoot, '..');
|
||||
const outputPath = join(workspaceRoot, 'server', 'src', 'web', 'embeddedAssets.generated.ts');
|
||||
const outputPath = join(workspaceRoot, 'hub', 'src', 'web', 'embeddedAssets.generated.ts');
|
||||
const contents = [
|
||||
'// This file is generated by cli/scripts/build-executable.ts when --with-web-assets is not used.',
|
||||
'// It intentionally contains no embedded assets.',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* WebSocket client for machine/runner communication with hapi-server
|
||||
* WebSocket client for machine/runner communication with hapi-hub
|
||||
*/
|
||||
|
||||
import { io, type Socket } from 'socket.io-client'
|
||||
|
||||
@@ -13,7 +13,7 @@ export async function codexLocalLauncher(session: CodexSession): Promise<'switch
|
||||
const exitFuture = new Future<void>();
|
||||
const resumeSessionId = session.sessionId;
|
||||
|
||||
// Start hapi server for MCP bridge (same as remote mode)
|
||||
// Start hapi hub for MCP bridge (same as remote mode)
|
||||
const { server: happyServer, mcpServers } = await buildHapiMcpBridge(session.client);
|
||||
logger.debug(`[codex-local]: Started hapi MCP bridge server at ${happyServer.url}`);
|
||||
|
||||
|
||||
@@ -75,8 +75,9 @@ ${chalk.bold('Usage:')}
|
||||
hapi mcp Start MCP stdio bridge
|
||||
hapi connect (not available in direct-connect mode)
|
||||
hapi notify (not available in direct-connect mode)
|
||||
hapi server Start the API + web server
|
||||
hapi server --relay Start with public relay
|
||||
hapi hub Start the API + web hub
|
||||
hapi hub --relay Start with public relay
|
||||
hapi server Alias for hapi hub
|
||||
hapi runner Manage background service that allows
|
||||
to spawn new sessions away from your computer
|
||||
hapi doctor System diagnostics & troubleshooting
|
||||
@@ -146,9 +147,9 @@ ${chalk.bold.cyan('Claude Code Options (from `claude --help`):')}
|
||||
messageLower.includes('enotfound') ||
|
||||
messageLower.includes('network error')
|
||||
) {
|
||||
console.error(chalk.yellow('Unable to connect to HAPI server'))
|
||||
console.error(chalk.gray(` Server URL: ${configuration.apiUrl}`))
|
||||
console.error(chalk.gray(' Please check your network connection or server status'))
|
||||
console.error(chalk.yellow('Unable to connect to HAPI hub'))
|
||||
console.error(chalk.gray(` Hub URL: ${configuration.apiUrl}`))
|
||||
console.error(chalk.gray(' Please check your network connection or hub status'))
|
||||
} else if (httpStatus === 403 && responseErrorText === 'Machine access denied') {
|
||||
console.error(chalk.red('Machine access denied.'))
|
||||
console.error(chalk.gray(' This machineId is already registered under a different namespace.'))
|
||||
@@ -171,9 +172,9 @@ ${chalk.bold.cyan('Claude Code Options (from `claude --help`):')}
|
||||
|
||||
if (serverProtocolVersion !== undefined && serverProtocolVersion !== PROTOCOL_VERSION) {
|
||||
if (serverProtocolVersion < PROTOCOL_VERSION) {
|
||||
console.error(chalk.yellow(` Hint: server protocol version (${serverProtocolVersion}) is behind CLI (${PROTOCOL_VERSION}). Please update the server.`))
|
||||
console.error(chalk.yellow(` Hint: hub protocol version (${serverProtocolVersion}) is behind CLI (${PROTOCOL_VERSION}). Please update the hub.`))
|
||||
} else {
|
||||
console.error(chalk.yellow(` Hint: CLI protocol version (${PROTOCOL_VERSION}) is behind server (${serverProtocolVersion}). Please update the CLI.`))
|
||||
console.error(chalk.yellow(` Hint: CLI protocol version (${PROTOCOL_VERSION}) is behind hub (${serverProtocolVersion}). Please update the CLI.`))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import chalk from 'chalk'
|
||||
import type { CommandDefinition, CommandContext } from './types'
|
||||
|
||||
function parseServerArgs(args: string[]): { host?: string; port?: string } {
|
||||
function parseHubArgs(args: string[]): { host?: string; port?: string } {
|
||||
const result: { host?: string; port?: string } = {}
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
@@ -20,12 +20,12 @@ function parseServerArgs(args: string[]): { host?: string; port?: string } {
|
||||
return result
|
||||
}
|
||||
|
||||
export const serverCommand: CommandDefinition = {
|
||||
name: 'server',
|
||||
export const hubCommand: CommandDefinition = {
|
||||
name: 'hub',
|
||||
requiresRuntimeAssets: true,
|
||||
run: async (context: CommandContext) => {
|
||||
try {
|
||||
const { host, port } = parseServerArgs(context.commandArgs)
|
||||
const { host, port } = parseHubArgs(context.commandArgs)
|
||||
|
||||
if (host) {
|
||||
process.env.WEBAPP_HOST = host
|
||||
@@ -33,7 +33,7 @@ export const serverCommand: CommandDefinition = {
|
||||
if (port) {
|
||||
process.env.WEBAPP_PORT = port
|
||||
}
|
||||
await import('../../../server/src/index')
|
||||
await import('../../../hub/src/index')
|
||||
} catch (error) {
|
||||
console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error')
|
||||
if (process.env.DEBUG) {
|
||||
@@ -6,7 +6,7 @@ export const notifyCommand: CommandDefinition = {
|
||||
requiresRuntimeAssets: true,
|
||||
run: async () => {
|
||||
console.error(chalk.red('The `hapi notify` command is not available in direct-connect mode.'))
|
||||
console.error(chalk.gray('Use Telegram notifications from hapi-server instead.'))
|
||||
console.error(chalk.gray('Use Telegram notifications from hapi-hub instead.'))
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { geminiCommand } from './gemini'
|
||||
import { hookForwarderCommand } from './hookForwarder'
|
||||
import { mcpCommand } from './mcp'
|
||||
import { notifyCommand } from './notify'
|
||||
import { serverCommand } from './server'
|
||||
import { hubCommand } from './hub'
|
||||
import type { CommandContext, CommandDefinition } from './types'
|
||||
|
||||
const COMMANDS: CommandDefinition[] = [
|
||||
@@ -17,7 +17,8 @@ const COMMANDS: CommandDefinition[] = [
|
||||
codexCommand,
|
||||
geminiCommand,
|
||||
mcpCommand,
|
||||
serverCommand,
|
||||
hubCommand,
|
||||
{ ...hubCommand, name: 'server' },
|
||||
hookForwarderCommand,
|
||||
doctorCommand,
|
||||
runnerCommand,
|
||||
|
||||
@@ -280,7 +280,7 @@ All data is plain JSON over TLS; authentication is `CLI_API_TOKEN` (no end-to-en
|
||||
|
||||
### Test Environment
|
||||
- Requires `.env.integration-test`
|
||||
- Uses local hapi-server (http://localhost:3006)
|
||||
- Uses local hapi-hub (http://localhost:3006)
|
||||
- Separate `~/.hapi-dev-test` home directory
|
||||
|
||||
### Key Test Scenarios
|
||||
@@ -296,7 +296,7 @@ All data is plain JSON over TLS; authentication is `CLI_API_TOKEN` (no end-to-en
|
||||
|
||||
# Machine Sync Architecture - Separated Metadata & Runner State
|
||||
|
||||
> Direct-connect note: the "server" is `hapi-server`, payloads are plain JSON (no base64/encryption),
|
||||
> Direct-connect note: the "hub" is `hapi-hub`, payloads are plain JSON (no base64/encryption),
|
||||
> and authentication uses `CLI_API_TOKEN` (REST `Authorization: Bearer ...` + Socket.IO `handshake.auth.token`).
|
||||
|
||||
## Data Structure (Similar to Session's metadata + agentState)
|
||||
@@ -327,7 +327,7 @@ interface RunnerState {
|
||||
|
||||
Checks if machine ID exists in settings:
|
||||
- If not: creates ID locally only (so sessions can reference it)
|
||||
- Does NOT create machine on server - that's runner's job
|
||||
- Does NOT create machine on hub - that's runner's job
|
||||
- CLI doesn't manage machine details - all API & schema live in runner subpackage
|
||||
|
||||
## 2. Runner Startup - Initial Registration
|
||||
@@ -444,10 +444,10 @@ socket.emit('machine-update-metadata', {
|
||||
}, callback)
|
||||
```
|
||||
|
||||
## 5. Mini App RPC Calls (via hapi-server)
|
||||
## 5. Mini App RPC Calls (via hapi-hub)
|
||||
|
||||
The Telegram Mini App calls REST endpoints on `hapi-server` (for example `POST /api/machines/:id/spawn`).
|
||||
`hapi-server` then relays those requests to the runner via Socket.IO `rpc-request` on the `/cli` namespace.
|
||||
The Telegram Mini App calls REST endpoints on `hapi-hub` (for example `POST /api/machines/:id/spawn`).
|
||||
`hapi-hub` then relays those requests to the runner via Socket.IO `rpc-request` on the `/cli` namespace.
|
||||
|
||||
RPC method naming (machine-scoped) uses a `${machineId}:` prefix, for example:
|
||||
- `${machineId}:spawn-happy-session`
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
*
|
||||
* The integration test environment uses .env.integration-test which sets:
|
||||
* - HAPI_HOME=~/.hapi-dev-test (DIFFERENT from dev's ~/.hapi-dev!)
|
||||
* - HAPI_API_URL=http://localhost:3006 (local hapi-server)
|
||||
* - CLI_API_TOKEN=... (must match the server)
|
||||
* - HAPI_API_URL=http://localhost:3006 (local hapi-hub)
|
||||
* - CLI_API_TOKEN=... (must match the hub)
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
@@ -48,7 +48,7 @@ async function waitFor(
|
||||
throw new Error('Timeout waiting for condition');
|
||||
}
|
||||
|
||||
// Check if dev server is running and properly configured
|
||||
// Check if dev hub is running and properly configured
|
||||
async function isServerHealthy(): Promise<boolean> {
|
||||
try {
|
||||
if (!configuration.cliApiToken) {
|
||||
|
||||
@@ -172,8 +172,8 @@ export function getTunwgPath(): string {
|
||||
return join(runtimePath(), 'tools', 'tunwg', tunwgBinary);
|
||||
}
|
||||
|
||||
// Development mode: use downloaded binary from server/tools/tunwg
|
||||
// Development mode: use downloaded binary from hub/tools/tunwg
|
||||
const platformDir = getPlatformDir();
|
||||
const devBinaryName = isWin ? `tunwg-${platformDir}.exe` : `tunwg-${platformDir}`;
|
||||
return join(__dirname, '..', '..', '..', 'server', 'tools', 'tunwg', devBinaryName);
|
||||
return join(__dirname, '..', '..', '..', 'hub', 'tools', 'tunwg', devBinaryName);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import difftasticArchiveLicense from '../../tools/archives/difftastic-LICENSE' a
|
||||
import ripgrepArchiveLicense from '../../tools/archives/ripgrep-LICENSE' assert { type: 'file' };
|
||||
import difftasticLicense from '../../tools/licenses/difftastic-LICENSE' assert { type: 'file' };
|
||||
import ripgrepLicense from '../../tools/licenses/ripgrep-LICENSE' assert { type: 'file' };
|
||||
import tunwgLicense from '../../../server/tools/tunwg/LICENSE' assert { type: 'file' };
|
||||
import tunwgLicense from '../../../hub/tools/tunwg/LICENSE' assert { type: 'file' };
|
||||
|
||||
export interface EmbeddedAsset {
|
||||
relativePath: string;
|
||||
@@ -35,7 +35,7 @@ async function selectEmbeddedAssets(): Promise<EmbeddedAsset[]> {
|
||||
] = await Promise.all([
|
||||
import('../../tools/archives/difftastic-arm64-darwin.tar.gz', { assert: { type: 'file' } }),
|
||||
import('../../tools/archives/ripgrep-arm64-darwin.tar.gz', { assert: { type: 'file' } }),
|
||||
import('../../../server/tools/tunwg/tunwg-arm64-darwin', { assert: { type: 'file' } })
|
||||
import('../../../hub/tools/tunwg/tunwg-arm64-darwin', { assert: { type: 'file' } })
|
||||
]);
|
||||
return [
|
||||
...COMMON_ASSETS,
|
||||
@@ -53,7 +53,7 @@ async function selectEmbeddedAssets(): Promise<EmbeddedAsset[]> {
|
||||
] = await Promise.all([
|
||||
import('../../tools/archives/difftastic-x64-darwin.tar.gz', { assert: { type: 'file' } }),
|
||||
import('../../tools/archives/ripgrep-x64-darwin.tar.gz', { assert: { type: 'file' } }),
|
||||
import('../../../server/tools/tunwg/tunwg-x64-darwin', { assert: { type: 'file' } })
|
||||
import('../../../hub/tools/tunwg/tunwg-x64-darwin', { assert: { type: 'file' } })
|
||||
]);
|
||||
return [
|
||||
...COMMON_ASSETS,
|
||||
@@ -71,7 +71,7 @@ async function selectEmbeddedAssets(): Promise<EmbeddedAsset[]> {
|
||||
] = await Promise.all([
|
||||
import('../../tools/archives/difftastic-arm64-linux.tar.gz', { assert: { type: 'file' } }),
|
||||
import('../../tools/archives/ripgrep-arm64-linux.tar.gz', { assert: { type: 'file' } }),
|
||||
import('../../../server/tools/tunwg/tunwg-arm64-linux', { assert: { type: 'file' } })
|
||||
import('../../../hub/tools/tunwg/tunwg-arm64-linux', { assert: { type: 'file' } })
|
||||
]);
|
||||
return [
|
||||
...COMMON_ASSETS,
|
||||
@@ -89,7 +89,7 @@ async function selectEmbeddedAssets(): Promise<EmbeddedAsset[]> {
|
||||
] = await Promise.all([
|
||||
import('../../tools/archives/difftastic-x64-linux.tar.gz', { assert: { type: 'file' } }),
|
||||
import('../../tools/archives/ripgrep-x64-linux.tar.gz', { assert: { type: 'file' } }),
|
||||
import('../../../server/tools/tunwg/tunwg-x64-linux', { assert: { type: 'file' } })
|
||||
import('../../../hub/tools/tunwg/tunwg-x64-linux', { assert: { type: 'file' } })
|
||||
]);
|
||||
return [
|
||||
...COMMON_ASSETS,
|
||||
@@ -107,7 +107,7 @@ async function selectEmbeddedAssets(): Promise<EmbeddedAsset[]> {
|
||||
] = await Promise.all([
|
||||
import('../../tools/archives/difftastic-x64-win32.tar.gz', { assert: { type: 'file' } }),
|
||||
import('../../tools/archives/ripgrep-x64-win32.tar.gz', { assert: { type: 'file' } }),
|
||||
import('../../../server/tools/tunwg/tunwg-x64-win32.exe', { assert: { type: 'file' } })
|
||||
import('../../../hub/tools/tunwg/tunwg-x64-win32.exe', { assert: { type: 'file' } })
|
||||
]);
|
||||
return [
|
||||
...COMMON_ASSETS,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* Auto-start server module
|
||||
* Auto-start hub module
|
||||
*
|
||||
* Automatically starts the HAPI server when CLI is launched
|
||||
* Automatically starts the HAPI hub when CLI is launched
|
||||
* if specific conditions are met:
|
||||
* 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 (hub was previously started)
|
||||
* 3. Port 3006 is not currently listening
|
||||
*/
|
||||
|
||||
@@ -52,7 +52,7 @@ async function checkPortListening(port: number, host: string = '127.0.0.1'): Pro
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if server is ready via health endpoint
|
||||
* Check if hub is ready via health endpoint
|
||||
*/
|
||||
async function checkServerHealth(url: string): Promise<boolean> {
|
||||
try {
|
||||
@@ -66,7 +66,7 @@ async function checkServerHealth(url: string): Promise<boolean> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for server to become ready
|
||||
* Wait for hub to become ready
|
||||
*/
|
||||
async function waitForServerReady(
|
||||
url: string,
|
||||
@@ -87,7 +87,7 @@ async function waitForServerReady(
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if server should be auto-started
|
||||
* Determine if hub should be auto-started
|
||||
*/
|
||||
async function shouldAutoStartServer(): Promise<boolean> {
|
||||
// Condition 1: HAPI_API_URL not set (using default localhost:3006)
|
||||
@@ -99,13 +99,13 @@ async function shouldAutoStartServer(): Promise<boolean> {
|
||||
// Condition 2: Check settings.json
|
||||
const settings = await readSettings()
|
||||
|
||||
// 2a: apiUrl is set in settings.json (user configured a specific server)
|
||||
// 2a: apiUrl is set in settings.json (user configured a specific hub)
|
||||
if (settings.apiUrl || settings.serverUrl) {
|
||||
logger.debug('[AUTO-START] apiUrl is set in settings.json, skipping auto-start')
|
||||
return false
|
||||
}
|
||||
|
||||
// 2b: cliApiToken exists in settings.json (server was previously started)
|
||||
// 2b: cliApiToken exists in settings.json (hub was previously started)
|
||||
if (!settings.cliApiToken) {
|
||||
logger.debug('[AUTO-START] No cliApiToken in settings, skipping auto-start')
|
||||
return false
|
||||
@@ -122,25 +122,25 @@ async function shouldAutoStartServer(): Promise<boolean> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Start server as a child process (will exit when CLI exits)
|
||||
* Start hub as a child process (will exit when CLI exits)
|
||||
*/
|
||||
function startServerAsChild(): void {
|
||||
const serverProcess = spawnHappyCLI(['server'], {
|
||||
const serverProcess = spawnHappyCLI(['hub'], {
|
||||
detached: false,
|
||||
stdio: 'ignore',
|
||||
env: process.env
|
||||
})
|
||||
|
||||
logger.debug(`[AUTO-START] Server process spawned with PID ${serverProcess.pid}`)
|
||||
logger.debug(`[AUTO-START] Hub process spawned with PID ${serverProcess.pid}`)
|
||||
|
||||
// Ensure server is killed when CLI exits
|
||||
// Ensure hub is killed when CLI exits
|
||||
process.on('exit', () => {
|
||||
serverProcess.kill()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point: auto-start server if conditions are met
|
||||
* Main entry point: auto-start hub if conditions are met
|
||||
*/
|
||||
export async function maybeAutoStartServer(): Promise<void> {
|
||||
try {
|
||||
@@ -149,23 +149,23 @@ export async function maybeAutoStartServer(): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
logger.debug('[AUTO-START] Starting server automatically...')
|
||||
console.log(chalk.gray('Starting HAPI server in background...'))
|
||||
logger.debug('[AUTO-START] Starting hub automatically...')
|
||||
console.log(chalk.gray('Starting HAPI hub in background...'))
|
||||
|
||||
startServerAsChild()
|
||||
|
||||
const isReady = await waitForServerReady(configuration.apiUrl)
|
||||
|
||||
if (!isReady) {
|
||||
console.log(chalk.yellow('Warning: Server did not start within expected time'))
|
||||
console.log(chalk.gray(' Try running `hapi server` manually to see errors'))
|
||||
console.log(chalk.yellow('Warning: Hub did not start within expected time'))
|
||||
console.log(chalk.gray(' Try running `hapi hub` manually to see errors'))
|
||||
return
|
||||
}
|
||||
|
||||
console.log(chalk.green('HAPI server started'))
|
||||
console.log(chalk.green('HAPI hub started'))
|
||||
} catch (error) {
|
||||
logger.debug('[AUTO-START] Error during server auto-start', error)
|
||||
console.log(chalk.yellow('Warning: Failed to auto-start server'))
|
||||
logger.debug('[AUTO-START] Error during hub auto-start', error)
|
||||
console.log(chalk.yellow('Warning: Failed to auto-start hub'))
|
||||
if (error instanceof Error) {
|
||||
console.log(chalk.gray(` Error: ${error.message}`))
|
||||
}
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.d.ts",
|
||||
"../server/src/**/*.ts",
|
||||
"../server/src/**/*.d.ts"
|
||||
"../hub/src/**/*.ts",
|
||||
"../hub/src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user