mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 22:19:42 +02:00
feat: add --https flag for self-signed TLS support
Enables the Web Notifications API when accessing via SSH tunnel or non-localhost URLs. Self-signed cert is auto-generated with openssl and stored in ~/.claudeman/certs/ for reuse across restarts. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+9
-3
@@ -493,15 +493,21 @@ program
|
||||
.command('web')
|
||||
.description('Start the web interface')
|
||||
.option('-p, --port <port>', 'Port to listen on', '3000')
|
||||
.option('--https', 'Enable HTTPS with a self-signed certificate (enables browser Notifications API)')
|
||||
.action(async (options) => {
|
||||
const { startWebServer } = await import('./web/server.js');
|
||||
const port = parseInt(options.port, 10);
|
||||
const https = !!options.https;
|
||||
const protocol = https ? 'https' : 'http';
|
||||
|
||||
console.log(chalk.cyan(`Starting Claudeman web interface on port ${port}...`));
|
||||
console.log(chalk.cyan(`Starting Claudeman web interface on port ${port}${https ? ' (HTTPS)' : ''}...`));
|
||||
|
||||
try {
|
||||
await startWebServer(port);
|
||||
console.log(chalk.green(`\n✓ Web interface running at http://localhost:${port}`));
|
||||
await startWebServer(port, https);
|
||||
console.log(chalk.green(`\n✓ Web interface running at ${protocol}://localhost:${port}`));
|
||||
if (https) {
|
||||
console.log(chalk.yellow(' Note: Accept the self-signed certificate in your browser on first visit'));
|
||||
}
|
||||
console.log(chalk.gray(' Press Ctrl+C to stop\n'));
|
||||
} catch (err) {
|
||||
console.error(chalk.red(`✗ Failed to start web server: ${getErrorMessage(err)}`));
|
||||
|
||||
+49
-6
@@ -15,6 +15,7 @@ import fastifyStatic from '@fastify/static';
|
||||
import { join, dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { existsSync, mkdirSync, writeFileSync, readdirSync, readFileSync } from 'node:fs';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { homedir, totalmem, freemem, loadavg, cpus } from 'node:os';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { Session, ClaudeMessage, type BackgroundTask, type RalphTrackerState, type RalphTodoItem } from '../session.js';
|
||||
@@ -129,6 +130,39 @@ function autoConfigureRalph(session: Session, workingDir: string, broadcast: (ev
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or generate a self-signed TLS certificate for HTTPS.
|
||||
* Certs are stored in ~/.claudeman/certs/ and reused across restarts.
|
||||
*/
|
||||
function getOrCreateSelfSignedCert(): { key: string; cert: string } {
|
||||
const certsDir = join(homedir(), '.claudeman', 'certs');
|
||||
const keyPath = join(certsDir, 'server.key');
|
||||
const certPath = join(certsDir, 'server.crt');
|
||||
|
||||
if (existsSync(keyPath) && existsSync(certPath)) {
|
||||
return {
|
||||
key: readFileSync(keyPath, 'utf-8'),
|
||||
cert: readFileSync(certPath, 'utf-8'),
|
||||
};
|
||||
}
|
||||
|
||||
mkdirSync(certsDir, { recursive: true });
|
||||
|
||||
// Generate self-signed cert valid for 365 days, covering localhost and common LAN access patterns
|
||||
execSync(
|
||||
`openssl req -x509 -newkey rsa:2048 -nodes ` +
|
||||
`-keyout "${keyPath}" -out "${certPath}" ` +
|
||||
`-days 365 -subj "/CN=claudeman" ` +
|
||||
`-addext "subjectAltName=DNS:localhost,IP:127.0.0.1,IP:0.0.0.0"`,
|
||||
{ stdio: 'pipe' }
|
||||
);
|
||||
|
||||
return {
|
||||
key: readFileSync(keyPath, 'utf-8'),
|
||||
cert: readFileSync(certPath, 'utf-8'),
|
||||
};
|
||||
}
|
||||
|
||||
export class WebServer extends EventEmitter {
|
||||
private app: FastifyInstance;
|
||||
private sessions: Map<string, Session> = new Map();
|
||||
@@ -138,6 +172,7 @@ export class WebServer extends EventEmitter {
|
||||
private sseClients: Set<FastifyReply> = new Set();
|
||||
private store = getStore();
|
||||
private port: number;
|
||||
private https: boolean;
|
||||
private screenManager: ScreenManager;
|
||||
// Terminal batching for performance
|
||||
private terminalBatches: Map<string, string> = new Map();
|
||||
@@ -159,10 +194,17 @@ export class WebServer extends EventEmitter {
|
||||
// Spawn1337 agent orchestrator
|
||||
private spawnOrchestrator: SpawnOrchestrator;
|
||||
|
||||
constructor(port: number = 3000) {
|
||||
constructor(port: number = 3000, https: boolean = false) {
|
||||
super();
|
||||
this.port = port;
|
||||
this.app = Fastify({ logger: false });
|
||||
this.https = https;
|
||||
|
||||
if (https) {
|
||||
const { key, cert } = getOrCreateSelfSignedCert();
|
||||
this.app = Fastify({ logger: false, https: { key, cert } });
|
||||
} else {
|
||||
this.app = Fastify({ logger: false });
|
||||
}
|
||||
this.screenManager = new ScreenManager();
|
||||
|
||||
// Set up screen manager event listeners
|
||||
@@ -2157,10 +2199,11 @@ export class WebServer extends EventEmitter {
|
||||
async start(): Promise<void> {
|
||||
await this.setupRoutes();
|
||||
await this.app.listen({ port: this.port, host: '0.0.0.0' });
|
||||
console.log(`Claudeman web interface running at http://localhost:${this.port}`);
|
||||
const protocol = this.https ? 'https' : 'http';
|
||||
console.log(`Claudeman web interface running at ${protocol}://localhost:${this.port}`);
|
||||
|
||||
// Set API URL for child processes (MCP server, spawned sessions)
|
||||
process.env.CLAUDEMAN_API_URL = `http://localhost:${this.port}`;
|
||||
process.env.CLAUDEMAN_API_URL = `${protocol}://localhost:${this.port}`;
|
||||
|
||||
// Start scheduled runs cleanup timer
|
||||
this.scheduledCleanupTimer = setInterval(() => {
|
||||
@@ -2405,8 +2448,8 @@ export class WebServer extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
export async function startWebServer(port: number = 3000): Promise<WebServer> {
|
||||
const server = new WebServer(port);
|
||||
export async function startWebServer(port: number = 3000, https: boolean = false): Promise<WebServer> {
|
||||
const server = new WebServer(port, https);
|
||||
await server.start();
|
||||
return server;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user