Merge main into massive-terminal-upgrade

Resolves merge conflicts:
- apps/server/src/routes/terminal/common.ts: Keep randomBytes import, use @automaker/utils for createLogger
- apps/ui/eslint.config.mjs: Use main's explicit globals list with XMLHttpRequest and MediaQueryListEvent additions
- apps/ui/src/components/views/terminal-view.tsx: Keep our terminal improvements (killAllSessions, beforeunload, better error handling)
- apps/ui/src/config/terminal-themes.ts: Keep our search highlight colors for all themes
- apps/ui/src/store/app-store.ts: Keep our terminal settings persistence improvements (merge function)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
SuperComboGamer
2025-12-21 20:27:44 -05:00
393 changed files with 32473 additions and 17974 deletions

View File

@@ -2,13 +2,10 @@
* Common utilities for features routes
*/
import { createLogger } from "../../lib/logger.js";
import {
getErrorMessage as getErrorMessageShared,
createLogError,
} from "../common.js";
import { createLogger } from '@automaker/utils';
import { getErrorMessage as getErrorMessageShared, createLogError } from '../common.js';
const logger = createLogger("Features");
const logger = createLogger('Features');
// Re-export shared utilities
export { getErrorMessageShared as getErrorMessage };

View File

@@ -2,26 +2,27 @@
* Features routes - HTTP API for feature management
*/
import { Router } from "express";
import { FeatureLoader } from "../../services/feature-loader.js";
import { createListHandler } from "./routes/list.js";
import { createGetHandler } from "./routes/get.js";
import { createCreateHandler } from "./routes/create.js";
import { createUpdateHandler } from "./routes/update.js";
import { createDeleteHandler } from "./routes/delete.js";
import { createAgentOutputHandler } from "./routes/agent-output.js";
import { createGenerateTitleHandler } from "./routes/generate-title.js";
import { Router } from 'express';
import { FeatureLoader } from '../../services/feature-loader.js';
import { validatePathParams } from '../../middleware/validate-paths.js';
import { createListHandler } from './routes/list.js';
import { createGetHandler } from './routes/get.js';
import { createCreateHandler } from './routes/create.js';
import { createUpdateHandler } from './routes/update.js';
import { createDeleteHandler } from './routes/delete.js';
import { createAgentOutputHandler } from './routes/agent-output.js';
import { createGenerateTitleHandler } from './routes/generate-title.js';
export function createFeaturesRoutes(featureLoader: FeatureLoader): Router {
const router = Router();
router.post("/list", createListHandler(featureLoader));
router.post("/get", createGetHandler(featureLoader));
router.post("/create", createCreateHandler(featureLoader));
router.post("/update", createUpdateHandler(featureLoader));
router.post("/delete", createDeleteHandler(featureLoader));
router.post("/agent-output", createAgentOutputHandler(featureLoader));
router.post("/generate-title", createGenerateTitleHandler());
router.post('/list', validatePathParams('projectPath'), createListHandler(featureLoader));
router.post('/get', validatePathParams('projectPath'), createGetHandler(featureLoader));
router.post('/create', validatePathParams('projectPath'), createCreateHandler(featureLoader));
router.post('/update', validatePathParams('projectPath'), createUpdateHandler(featureLoader));
router.post('/delete', validatePathParams('projectPath'), createDeleteHandler(featureLoader));
router.post('/agent-output', createAgentOutputHandler(featureLoader));
router.post('/generate-title', createGenerateTitleHandler());
return router;
}

View File

@@ -2,13 +2,10 @@
* POST /create endpoint - Create a new feature
*/
import type { Request, Response } from "express";
import {
FeatureLoader,
type Feature,
} from "../../../services/feature-loader.js";
import { addAllowedPath } from "../../../lib/security.js";
import { getErrorMessage, logError } from "../common.js";
import type { Request, Response } from 'express';
import { FeatureLoader } from '../../../services/feature-loader.js';
import type { Feature } from '@automaker/types';
import { getErrorMessage, logError } from '../common.js';
export function createCreateHandler(featureLoader: FeatureLoader) {
return async (req: Request, res: Response): Promise<void> => {
@@ -19,22 +16,17 @@ export function createCreateHandler(featureLoader: FeatureLoader) {
};
if (!projectPath || !feature) {
res
.status(400)
.json({
success: false,
error: "projectPath and feature are required",
});
res.status(400).json({
success: false,
error: 'projectPath and feature are required',
});
return;
}
// Add project path to allowed paths
addAllowedPath(projectPath);
const created = await featureLoader.create(projectPath, feature);
res.json({ success: true, feature: created });
} catch (error) {
logError(error, "Create feature failed");
logError(error, 'Create feature failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};

View File

@@ -4,12 +4,12 @@
* Uses Claude Haiku to generate a short, descriptive title from feature description.
*/
import type { Request, Response } from "express";
import { query } from "@anthropic-ai/claude-agent-sdk";
import { createLogger } from "../../../lib/logger.js";
import { CLAUDE_MODEL_MAP } from "../../../lib/model-resolver.js";
import type { Request, Response } from 'express';
import { query } from '@anthropic-ai/claude-agent-sdk';
import { createLogger } from '@automaker/utils';
import { CLAUDE_MODEL_MAP } from '@automaker/model-resolver';
const logger = createLogger("GenerateTitle");
const logger = createLogger('GenerateTitle');
interface GenerateTitleRequestBody {
description: string;
@@ -44,16 +44,16 @@ async function extractTextFromStream(
};
}>
): Promise<string> {
let responseText = "";
let responseText = '';
for await (const msg of stream) {
if (msg.type === "assistant" && msg.message?.content) {
if (msg.type === 'assistant' && msg.message?.content) {
for (const block of msg.message.content) {
if (block.type === "text" && block.text) {
if (block.type === 'text' && block.text) {
responseText += block.text;
}
}
} else if (msg.type === "result" && msg.subtype === "success") {
} else if (msg.type === 'result' && msg.subtype === 'success') {
responseText = msg.result || responseText;
}
}
@@ -61,18 +61,15 @@ async function extractTextFromStream(
return responseText;
}
export function createGenerateTitleHandler(): (
req: Request,
res: Response
) => Promise<void> {
export function createGenerateTitleHandler(): (req: Request, res: Response) => Promise<void> {
return async (req: Request, res: Response): Promise<void> => {
try {
const { description } = req.body as GenerateTitleRequestBody;
if (!description || typeof description !== "string") {
if (!description || typeof description !== 'string') {
const response: GenerateTitleErrorResponse = {
success: false,
error: "description is required and must be a string",
error: 'description is required and must be a string',
};
res.status(400).json(response);
return;
@@ -82,7 +79,7 @@ export function createGenerateTitleHandler(): (
if (trimmedDescription.length === 0) {
const response: GenerateTitleErrorResponse = {
success: false,
error: "description cannot be empty",
error: 'description cannot be empty',
};
res.status(400).json(response);
return;
@@ -99,17 +96,17 @@ export function createGenerateTitleHandler(): (
systemPrompt: SYSTEM_PROMPT,
maxTurns: 1,
allowedTools: [],
permissionMode: "acceptEdits",
permissionMode: 'acceptEdits',
},
});
const title = await extractTextFromStream(stream);
if (!title || title.trim().length === 0) {
logger.warn("Received empty response from Claude");
logger.warn('Received empty response from Claude');
const response: GenerateTitleErrorResponse = {
success: false,
error: "Failed to generate title - empty response",
error: 'Failed to generate title - empty response',
};
res.status(500).json(response);
return;
@@ -123,9 +120,8 @@ export function createGenerateTitleHandler(): (
};
res.json(response);
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "Unknown error occurred";
logger.error("Title generation failed:", errorMessage);
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Title generation failed:', errorMessage);
const response: GenerateTitleErrorResponse = {
success: false,

View File

@@ -2,10 +2,9 @@
* POST /list endpoint - List all features for a project
*/
import type { Request, Response } from "express";
import { FeatureLoader } from "../../../services/feature-loader.js";
import { addAllowedPath } from "../../../lib/security.js";
import { getErrorMessage, logError } from "../common.js";
import type { Request, Response } from 'express';
import { FeatureLoader } from '../../../services/feature-loader.js';
import { getErrorMessage, logError } from '../common.js';
export function createListHandler(featureLoader: FeatureLoader) {
return async (req: Request, res: Response): Promise<void> => {
@@ -13,19 +12,14 @@ export function createListHandler(featureLoader: FeatureLoader) {
const { projectPath } = req.body as { projectPath: string };
if (!projectPath) {
res
.status(400)
.json({ success: false, error: "projectPath is required" });
res.status(400).json({ success: false, error: 'projectPath is required' });
return;
}
// Add project path to allowed paths
addAllowedPath(projectPath);
const features = await featureLoader.getAll(projectPath);
res.json({ success: true, features });
} catch (error) {
logError(error, "List features failed");
logError(error, 'List features failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};

View File

@@ -2,12 +2,10 @@
* POST /update endpoint - Update a feature
*/
import type { Request, Response } from "express";
import {
FeatureLoader,
type Feature,
} from "../../../services/feature-loader.js";
import { getErrorMessage, logError } from "../common.js";
import type { Request, Response } from 'express';
import { FeatureLoader } from '../../../services/feature-loader.js';
import type { Feature } from '@automaker/types';
import { getErrorMessage, logError } from '../common.js';
export function createUpdateHandler(featureLoader: FeatureLoader) {
return async (req: Request, res: Response): Promise<void> => {
@@ -21,19 +19,15 @@ export function createUpdateHandler(featureLoader: FeatureLoader) {
if (!projectPath || !featureId || !updates) {
res.status(400).json({
success: false,
error: "projectPath, featureId, and updates are required",
error: 'projectPath, featureId, and updates are required',
});
return;
}
const updated = await featureLoader.update(
projectPath,
featureId,
updates
);
const updated = await featureLoader.update(projectPath, featureId, updates);
res.json({ success: true, feature: updated });
} catch (error) {
logError(error, "Update feature failed");
logError(error, 'Update feature failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};