feat: add phase 1 runnable skeleton

This commit is contained in:
Hermes Agent
2026-07-09 04:19:23 +00:00
parent deec0c7fa3
commit bd0cf34aa3
35 changed files with 675 additions and 5 deletions
+1
View File
@@ -0,0 +1 @@
{"name":"@hermes-mobile/companion","version":"0.1.0","private":true,"type":"module","main":"dist/index.js","scripts":{"dev":"tsx watch src/index.ts","build":"tsc -p tsconfig.json","start":"node dist/index.js","typecheck":"tsc -p tsconfig.json --noEmit","lint":"eslint src"},"dependencies":{"@fastify/cors":"^10.0.1","@hermes-mobile/shared":"workspace:*","fastify":"^5.2.1"},"devDependencies":{"tsx":"^4.19.2"}}
+20
View File
@@ -0,0 +1,20 @@
import cors from '@fastify/cors';
import Fastify from 'fastify';
import { registerHealthRoutes } from './routes/health.js';
export async function buildApp() {
const app = Fastify({ logger: true });
await app.register(cors, {
origin: true,
});
app.get('/', async () => ({
service: 'hermes-mobile-companion',
health: '/api/health',
}));
await registerHealthRoutes(app);
return app;
}
+13
View File
@@ -0,0 +1,13 @@
import { buildApp } from './app.js';
const port = Number.parseInt(process.env.PORT ?? '8787', 10);
const host = process.env.HOST ?? '0.0.0.0';
const app = await buildApp();
try {
await app.listen({ port, host });
} catch (error) {
app.log.error(error);
process.exit(1);
}
+21
View File
@@ -0,0 +1,21 @@
import type { FastifyInstance } from 'fastify';
import { healthResponseSchema } from '@hermes-mobile/shared';
import { getHermesHealth } from '../system/hermesHealth.js';
export async function registerHealthRoutes(app: FastifyInstance): Promise<void> {
app.get('/api/health', async () => {
const response = {
ok: true,
service: 'hermes-mobile-companion' as const,
version: process.env.npm_package_version ?? '0.1.0',
mode: process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test' ? process.env.NODE_ENV : 'development',
uptimeSeconds: process.uptime(),
timestamp: new Date().toISOString(),
checks: {
hermes: await getHermesHealth(),
},
};
return healthResponseSchema.parse(response);
});
}
+26
View File
@@ -0,0 +1,26 @@
import { access } from 'node:fs/promises';
import { delimiter } from 'node:path';
import { env } from 'node:process';
import type { HermesHealth } from '@hermes-mobile/shared';
async function isExecutable(path: string): Promise<boolean> {
try {
await access(path);
return true;
} catch {
return false;
}
}
export async function getHermesHealth(): Promise<HermesHealth> {
const pathEntries = env.PATH?.split(delimiter).filter(Boolean) ?? [];
for (const pathEntry of pathEntries) {
const cliPath = `${pathEntry}/hermes`;
if (await isExecutable(cliPath)) {
return { cliAvailable: true, cliPath };
}
}
return { cliAvailable: false };
}
+1
View File
@@ -0,0 +1 @@
{"extends":"../../tsconfig.base.json","compilerOptions":{"outDir":"dist","rootDir":"src","noEmit":false},"references":[{"path":"../../packages/shared"}],"include":["src"]}