docs: Add comprehensive JSDoc docstrings to settings module (80% coverage)

This commit addresses CodeRabbit feedback from PR #186 by adding detailed
documentation to all public APIs in the settings module:

**Server-side documentation:**
- SettingsService class: 12 public methods with parameter and return types
- Settings types (settings.ts): All type aliases, interfaces, and constants
  documented with usage context
- Route handlers (8 endpoints): Complete endpoint documentation with request/response
  schemas
- Automaker paths utilities: All 13 path resolution functions fully documented

**Client-side documentation:**
- useSettingsMigration hook: Migration flow and state documented
- Sync functions: Three sync helpers (settings, credentials, project) with usage guidelines
- localStorage constants: Clear documentation of migration keys and cleanup strategy

All docstrings follow JSDoc format with:
- Purpose and behavior description
- Parameter documentation with types
- Return value documentation
- Usage examples where applicable
- Cross-references between related functions

This improves code maintainability, IDE autocomplete, and developer onboarding.

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

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Test User
2025-12-20 09:54:30 -05:00
parent ba7904c189
commit e29880254e
14 changed files with 640 additions and 76 deletions

View File

@@ -1,15 +1,25 @@
/** /**
* Automaker Paths - Utilities for managing automaker data storage * Automaker Paths - Utilities for managing automaker data storage
* *
* Stores project data inside the project directory at {projectPath}/.automaker/ * Provides functions to construct paths for:
* - Project-level data stored in {projectPath}/.automaker/
* - Global user data stored in app userData directory
*
* All returned paths are absolute and ready to use with fs module.
* Directory creation is handled separately by ensure* functions.
*/ */
import fs from "fs/promises"; import fs from "fs/promises";
import path from "path"; import path from "path";
/** /**
* Get the automaker data directory for a project * Get the automaker data directory root for a project
* This is stored inside the project at .automaker/ *
* All project-specific automaker data is stored under {projectPath}/.automaker/
* This directory is created when needed via ensureAutomakerDir().
*
* @param projectPath - Absolute path to project directory
* @returns Absolute path to {projectPath}/.automaker
*/ */
export function getAutomakerDir(projectPath: string): string { export function getAutomakerDir(projectPath: string): string {
return path.join(projectPath, ".automaker"); return path.join(projectPath, ".automaker");
@@ -17,6 +27,11 @@ export function getAutomakerDir(projectPath: string): string {
/** /**
* Get the features directory for a project * Get the features directory for a project
*
* Contains subdirectories for each feature, keyed by featureId.
*
* @param projectPath - Absolute path to project directory
* @returns Absolute path to {projectPath}/.automaker/features
*/ */
export function getFeaturesDir(projectPath: string): string { export function getFeaturesDir(projectPath: string): string {
return path.join(getAutomakerDir(projectPath), "features"); return path.join(getAutomakerDir(projectPath), "features");
@@ -24,6 +39,12 @@ export function getFeaturesDir(projectPath: string): string {
/** /**
* Get the directory for a specific feature * Get the directory for a specific feature
*
* Contains feature-specific data like generated code, tests, and logs.
*
* @param projectPath - Absolute path to project directory
* @param featureId - Feature identifier
* @returns Absolute path to {projectPath}/.automaker/features/{featureId}
*/ */
export function getFeatureDir(projectPath: string, featureId: string): string { export function getFeatureDir(projectPath: string, featureId: string): string {
return path.join(getFeaturesDir(projectPath), featureId); return path.join(getFeaturesDir(projectPath), featureId);
@@ -31,6 +52,12 @@ export function getFeatureDir(projectPath: string, featureId: string): string {
/** /**
* Get the images directory for a feature * Get the images directory for a feature
*
* Stores screenshots, diagrams, or other images related to the feature.
*
* @param projectPath - Absolute path to project directory
* @param featureId - Feature identifier
* @returns Absolute path to {projectPath}/.automaker/features/{featureId}/images
*/ */
export function getFeatureImagesDir( export function getFeatureImagesDir(
projectPath: string, projectPath: string,
@@ -40,21 +67,36 @@ export function getFeatureImagesDir(
} }
/** /**
* Get the board directory for a project (board backgrounds, etc.) * Get the board directory for a project
*
* Contains board-related data like background images and customization files.
*
* @param projectPath - Absolute path to project directory
* @returns Absolute path to {projectPath}/.automaker/board
*/ */
export function getBoardDir(projectPath: string): string { export function getBoardDir(projectPath: string): string {
return path.join(getAutomakerDir(projectPath), "board"); return path.join(getAutomakerDir(projectPath), "board");
} }
/** /**
* Get the images directory for a project (general images) * Get the general images directory for a project
*
* Stores project-level images like background images or shared assets.
*
* @param projectPath - Absolute path to project directory
* @returns Absolute path to {projectPath}/.automaker/images
*/ */
export function getImagesDir(projectPath: string): string { export function getImagesDir(projectPath: string): string {
return path.join(getAutomakerDir(projectPath), "images"); return path.join(getAutomakerDir(projectPath), "images");
} }
/** /**
* Get the context files directory for a project (user-added context files) * Get the context files directory for a project
*
* Stores user-uploaded context files for reference during generation.
*
* @param projectPath - Absolute path to project directory
* @returns Absolute path to {projectPath}/.automaker/context
*/ */
export function getContextDir(projectPath: string): string { export function getContextDir(projectPath: string): string {
return path.join(getAutomakerDir(projectPath), "context"); return path.join(getAutomakerDir(projectPath), "context");
@@ -62,6 +104,11 @@ export function getContextDir(projectPath: string): string {
/** /**
* Get the worktrees metadata directory for a project * Get the worktrees metadata directory for a project
*
* Stores information about git worktrees associated with the project.
*
* @param projectPath - Absolute path to project directory
* @returns Absolute path to {projectPath}/.automaker/worktrees
*/ */
export function getWorktreesDir(projectPath: string): string { export function getWorktreesDir(projectPath: string): string {
return path.join(getAutomakerDir(projectPath), "worktrees"); return path.join(getAutomakerDir(projectPath), "worktrees");
@@ -69,6 +116,11 @@ export function getWorktreesDir(projectPath: string): string {
/** /**
* Get the app spec file path for a project * Get the app spec file path for a project
*
* Stores the application specification document used for generation.
*
* @param projectPath - Absolute path to project directory
* @returns Absolute path to {projectPath}/.automaker/app_spec.txt
*/ */
export function getAppSpecPath(projectPath: string): string { export function getAppSpecPath(projectPath: string): string {
return path.join(getAutomakerDir(projectPath), "app_spec.txt"); return path.join(getAutomakerDir(projectPath), "app_spec.txt");
@@ -76,13 +128,24 @@ export function getAppSpecPath(projectPath: string): string {
/** /**
* Get the branch tracking file path for a project * Get the branch tracking file path for a project
*
* Stores JSON metadata about active git branches and worktrees.
*
* @param projectPath - Absolute path to project directory
* @returns Absolute path to {projectPath}/.automaker/active-branches.json
*/ */
export function getBranchTrackingPath(projectPath: string): string { export function getBranchTrackingPath(projectPath: string): string {
return path.join(getAutomakerDir(projectPath), "active-branches.json"); return path.join(getAutomakerDir(projectPath), "active-branches.json");
} }
/** /**
* Ensure the automaker directory structure exists for a project * Create the automaker directory structure for a project if it doesn't exist
*
* Creates {projectPath}/.automaker with all subdirectories recursively.
* Safe to call multiple times - uses recursive: true.
*
* @param projectPath - Absolute path to project directory
* @returns Promise resolving to the created automaker directory path
*/ */
export async function ensureAutomakerDir(projectPath: string): Promise<string> { export async function ensureAutomakerDir(projectPath: string): Promise<string> {
const automakerDir = getAutomakerDir(projectPath); const automakerDir = getAutomakerDir(projectPath);
@@ -96,29 +159,56 @@ export async function ensureAutomakerDir(projectPath: string): Promise<string> {
/** /**
* Get the global settings file path * Get the global settings file path
* DATA_DIR is typically ~/Library/Application Support/automaker (macOS) *
* or %APPDATA%\automaker (Windows) or ~/.config/automaker (Linux) * Stores user preferences, keyboard shortcuts, AI profiles, and project history.
* Located in the platform-specific userData directory.
*
* Default locations:
* - macOS: ~/Library/Application Support/automaker
* - Windows: %APPDATA%\automaker
* - Linux: ~/.config/automaker
*
* @param dataDir - User data directory (from app.getPath('userData'))
* @returns Absolute path to {dataDir}/settings.json
*/ */
export function getGlobalSettingsPath(dataDir: string): string { export function getGlobalSettingsPath(dataDir: string): string {
return path.join(dataDir, "settings.json"); return path.join(dataDir, "settings.json");
} }
/** /**
* Get the credentials file path (separate from settings for security) * Get the credentials file path
*
* Stores sensitive API keys separately from other settings for security.
* Located in the platform-specific userData directory.
*
* @param dataDir - User data directory (from app.getPath('userData'))
* @returns Absolute path to {dataDir}/credentials.json
*/ */
export function getCredentialsPath(dataDir: string): string { export function getCredentialsPath(dataDir: string): string {
return path.join(dataDir, "credentials.json"); return path.join(dataDir, "credentials.json");
} }
/** /**
* Get the project settings file path within a project's .automaker directory * Get the project settings file path
*
* Stores project-specific settings that override global settings.
* Located within the project's .automaker directory.
*
* @param projectPath - Absolute path to project directory
* @returns Absolute path to {projectPath}/.automaker/settings.json
*/ */
export function getProjectSettingsPath(projectPath: string): string { export function getProjectSettingsPath(projectPath: string): string {
return path.join(getAutomakerDir(projectPath), "settings.json"); return path.join(getAutomakerDir(projectPath), "settings.json");
} }
/** /**
* Ensure the global data directory exists * Create the global data directory if it doesn't exist
*
* Creates the userData directory for storing global settings and credentials.
* Safe to call multiple times - uses recursive: true.
*
* @param dataDir - User data directory path to create
* @returns Promise resolving to the created data directory path
*/ */
export async function ensureDataDir(dataDir: string): Promise<string> { export async function ensureDataDir(dataDir: string): Promise<string> {
await fs.mkdir(dataDir, { recursive: true }); await fs.mkdir(dataDir, { recursive: true });

View File

@@ -1,5 +1,8 @@
/** /**
* Common utilities for settings routes * Common utilities for settings routes
*
* Provides logger and error handling utilities shared across all settings endpoints.
* Re-exports error handling helpers from the parent routes module.
*/ */
import { createLogger } from "../../lib/logger.js"; import { createLogger } from "../../lib/logger.js";
@@ -8,8 +11,19 @@ import {
createLogError, createLogError,
} from "../common.js"; } from "../common.js";
/** Logger instance for settings-related operations */
export const logger = createLogger("Settings"); export const logger = createLogger("Settings");
// Re-export shared utilities /**
* Extract user-friendly error message from error objects
*
* Re-exported from parent routes common module for consistency.
*/
export { getErrorMessageShared as getErrorMessage }; export { getErrorMessageShared as getErrorMessage };
/**
* Log error with automatic logger binding
*
* Convenience function for logging errors with the Settings logger.
*/
export const logError = createLogError(logger); export const logError = createLogError(logger);

View File

@@ -1,5 +1,15 @@
/** /**
* Settings routes - HTTP API for persistent file-based settings * Settings routes - HTTP API for persistent file-based settings
*
* Provides endpoints for:
* - Status checking (migration readiness)
* - Global settings CRUD
* - Credentials management
* - Project-specific settings
* - localStorage to file migration
*
* All endpoints use handler factories that receive the SettingsService instance.
* Mounted at /api/settings in the main server.
*/ */
import { Router } from "express"; import { Router } from "express";
@@ -13,6 +23,25 @@ import { createUpdateProjectHandler } from "./routes/update-project.js";
import { createMigrateHandler } from "./routes/migrate.js"; import { createMigrateHandler } from "./routes/migrate.js";
import { createStatusHandler } from "./routes/status.js"; import { createStatusHandler } from "./routes/status.js";
/**
* Create settings router with all endpoints
*
* Registers handlers for all settings-related HTTP endpoints.
* Each handler is created with the provided SettingsService instance.
*
* Endpoints:
* - GET /status - Check migration status and data availability
* - GET /global - Get global settings
* - PUT /global - Update global settings
* - GET /credentials - Get masked credentials (safe for UI)
* - PUT /credentials - Update API keys
* - POST /project - Get project settings (requires projectPath in body)
* - PUT /project - Update project settings
* - POST /migrate - Migrate settings from localStorage
*
* @param settingsService - Instance of SettingsService for file I/O
* @returns Express Router configured with all settings endpoints
*/
export function createSettingsRoutes(settingsService: SettingsService): Router { export function createSettingsRoutes(settingsService: SettingsService): Router {
const router = Router(); const router = Router();

View File

@@ -1,11 +1,23 @@
/** /**
* GET /api/settings/credentials - Get credentials (masked for security) * GET /api/settings/credentials - Get API key status (masked for security)
*
* Returns masked credentials showing which providers have keys configured.
* Each provider shows: `{ configured: boolean, masked: string }`
* Masked shows first 4 and last 4 characters for verification.
*
* Response: `{ "success": true, "credentials": { anthropic, google, openai } }`
*/ */
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import type { SettingsService } from "../../../services/settings-service.js"; import type { SettingsService } from "../../../services/settings-service.js";
import { getErrorMessage, logError } from "../common.js"; import { getErrorMessage, logError } from "../common.js";
/**
* Create handler factory for GET /api/settings/credentials
*
* @param settingsService - Instance of SettingsService for file I/O
* @returns Express request handler
*/
export function createGetCredentialsHandler(settingsService: SettingsService) { export function createGetCredentialsHandler(settingsService: SettingsService) {
return async (_req: Request, res: Response): Promise<void> => { return async (_req: Request, res: Response): Promise<void> => {
try { try {

View File

@@ -1,11 +1,22 @@
/** /**
* GET /api/settings/global - Get global settings * GET /api/settings/global - Retrieve global user settings
*
* Returns the complete GlobalSettings object with all user preferences,
* keyboard shortcuts, AI profiles, and project history.
*
* Response: `{ "success": true, "settings": GlobalSettings }`
*/ */
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import type { SettingsService } from "../../../services/settings-service.js"; import type { SettingsService } from "../../../services/settings-service.js";
import { getErrorMessage, logError } from "../common.js"; import { getErrorMessage, logError } from "../common.js";
/**
* Create handler factory for GET /api/settings/global
*
* @param settingsService - Instance of SettingsService for file I/O
* @returns Express request handler
*/
export function createGetGlobalHandler(settingsService: SettingsService) { export function createGetGlobalHandler(settingsService: SettingsService) {
return async (_req: Request, res: Response): Promise<void> => { return async (_req: Request, res: Response): Promise<void> => {
try { try {

View File

@@ -1,12 +1,23 @@
/** /**
* POST /api/settings/project - Get project settings * POST /api/settings/project - Get project-specific settings
* Uses POST because projectPath may contain special characters *
* Retrieves settings overrides for a specific project. Uses POST because
* projectPath may contain special characters that don't work well in URLs.
*
* Request body: `{ projectPath: string }`
* Response: `{ "success": true, "settings": ProjectSettings }`
*/ */
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import type { SettingsService } from "../../../services/settings-service.js"; import type { SettingsService } from "../../../services/settings-service.js";
import { getErrorMessage, logError } from "../common.js"; import { getErrorMessage, logError } from "../common.js";
/**
* Create handler factory for POST /api/settings/project
*
* @param settingsService - Instance of SettingsService for file I/O
* @returns Express request handler
*/
export function createGetProjectHandler(settingsService: SettingsService) { export function createGetProjectHandler(settingsService: SettingsService) {
return async (req: Request, res: Response): Promise<void> => { return async (req: Request, res: Response): Promise<void> => {
try { try {

View File

@@ -1,11 +1,45 @@
/** /**
* POST /api/settings/migrate - Migrate settings from localStorage * POST /api/settings/migrate - Migrate settings from localStorage to file storage
*
* Called during onboarding when UI detects localStorage data but no settings files.
* Extracts settings from various localStorage keys and writes to new file structure.
* Collects errors but continues on partial failures (graceful degradation).
*
* Request body:
* ```json
* {
* "data": {
* "automaker-storage"?: string,
* "automaker-setup"?: string,
* "worktree-panel-collapsed"?: string,
* "file-browser-recent-folders"?: string,
* "automaker:lastProjectDir"?: string
* }
* }
* ```
*
* Response:
* ```json
* {
* "success": boolean,
* "migratedGlobalSettings": boolean,
* "migratedCredentials": boolean,
* "migratedProjectCount": number,
* "errors": string[]
* }
* ```
*/ */
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import type { SettingsService } from "../../../services/settings-service.js"; import type { SettingsService } from "../../../services/settings-service.js";
import { getErrorMessage, logError, logger } from "../common.js"; import { getErrorMessage, logError, logger } from "../common.js";
/**
* Create handler factory for POST /api/settings/migrate
*
* @param settingsService - Instance of SettingsService for file I/O
* @returns Express request handler
*/
export function createMigrateHandler(settingsService: SettingsService) { export function createMigrateHandler(settingsService: SettingsService) {
return async (req: Request, res: Response): Promise<void> => { return async (req: Request, res: Response): Promise<void> => {
try { try {

View File

@@ -1,12 +1,31 @@
/** /**
* GET /api/settings/status - Get settings migration status * GET /api/settings/status - Get settings migration and availability status
* Returns whether settings files exist (to determine if migration is needed) *
* Checks which settings files exist to determine if migration from localStorage
* is needed. Used by UI during onboarding to decide whether to show migration flow.
*
* Response:
* ```json
* {
* "success": true,
* "hasGlobalSettings": boolean,
* "hasCredentials": boolean,
* "dataDir": string,
* "needsMigration": boolean
* }
* ```
*/ */
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import type { SettingsService } from "../../../services/settings-service.js"; import type { SettingsService } from "../../../services/settings-service.js";
import { getErrorMessage, logError } from "../common.js"; import { getErrorMessage, logError } from "../common.js";
/**
* Create handler factory for GET /api/settings/status
*
* @param settingsService - Instance of SettingsService for file I/O
* @returns Express request handler
*/
export function createStatusHandler(settingsService: SettingsService) { export function createStatusHandler(settingsService: SettingsService) {
return async (_req: Request, res: Response): Promise<void> => { return async (_req: Request, res: Response): Promise<void> => {
try { try {

View File

@@ -1,5 +1,11 @@
/** /**
* PUT /api/settings/credentials - Update credentials * PUT /api/settings/credentials - Update API credentials
*
* Updates API keys for Anthropic, Google, or OpenAI. Partial updates supported.
* Returns masked credentials for verification without exposing full keys.
*
* Request body: `Partial<Credentials>` (usually just apiKeys)
* Response: `{ "success": true, "credentials": { anthropic, google, openai } }`
*/ */
import type { Request, Response } from "express"; import type { Request, Response } from "express";
@@ -7,6 +13,12 @@ import type { SettingsService } from "../../../services/settings-service.js";
import type { Credentials } from "../../../types/settings.js"; import type { Credentials } from "../../../types/settings.js";
import { getErrorMessage, logError } from "../common.js"; import { getErrorMessage, logError } from "../common.js";
/**
* Create handler factory for PUT /api/settings/credentials
*
* @param settingsService - Instance of SettingsService for file I/O
* @returns Express request handler
*/
export function createUpdateCredentialsHandler( export function createUpdateCredentialsHandler(
settingsService: SettingsService settingsService: SettingsService
) { ) {

View File

@@ -1,5 +1,11 @@
/** /**
* PUT /api/settings/global - Update global settings * PUT /api/settings/global - Update global user settings
*
* Accepts partial GlobalSettings update. Fields provided are merged into
* existing settings (not replaced). Returns updated settings.
*
* Request body: `Partial<GlobalSettings>`
* Response: `{ "success": true, "settings": GlobalSettings }`
*/ */
import type { Request, Response } from "express"; import type { Request, Response } from "express";
@@ -7,6 +13,12 @@ import type { SettingsService } from "../../../services/settings-service.js";
import type { GlobalSettings } from "../../../types/settings.js"; import type { GlobalSettings } from "../../../types/settings.js";
import { getErrorMessage, logError } from "../common.js"; import { getErrorMessage, logError } from "../common.js";
/**
* Create handler factory for PUT /api/settings/global
*
* @param settingsService - Instance of SettingsService for file I/O
* @returns Express request handler
*/
export function createUpdateGlobalHandler(settingsService: SettingsService) { export function createUpdateGlobalHandler(settingsService: SettingsService) {
return async (req: Request, res: Response): Promise<void> => { return async (req: Request, res: Response): Promise<void> => {
try { try {

View File

@@ -1,5 +1,11 @@
/** /**
* PUT /api/settings/project - Update project settings * PUT /api/settings/project - Update project-specific settings
*
* Updates settings for a specific project. Partial updates supported.
* Project settings override global settings when present.
*
* Request body: `{ projectPath: string, updates: Partial<ProjectSettings> }`
* Response: `{ "success": true, "settings": ProjectSettings }`
*/ */
import type { Request, Response } from "express"; import type { Request, Response } from "express";
@@ -7,6 +13,12 @@ import type { SettingsService } from "../../../services/settings-service.js";
import type { ProjectSettings } from "../../../types/settings.js"; import type { ProjectSettings } from "../../../types/settings.js";
import { getErrorMessage, logError } from "../common.js"; import { getErrorMessage, logError } from "../common.js";
/**
* Create handler factory for PUT /api/settings/project
*
* @param settingsService - Instance of SettingsService for file I/O
* @returns Express request handler
*/
export function createUpdateProjectHandler(settingsService: SettingsService) { export function createUpdateProjectHandler(settingsService: SettingsService) {
return async (req: Request, res: Response): Promise<void> => { return async (req: Request, res: Response): Promise<void> => {
try { try {

View File

@@ -88,9 +88,27 @@ async function fileExists(filePath: string): Promise<boolean> {
} }
} }
/**
* SettingsService - Manages persistent storage of user settings and credentials
*
* Handles reading and writing settings to JSON files with atomic operations
* for reliability. Provides three levels of settings:
* - Global settings: shared preferences in {dataDir}/settings.json
* - Credentials: sensitive API keys in {dataDir}/credentials.json
* - Project settings: per-project overrides in {projectPath}/.automaker/settings.json
*
* All operations are atomic (write to temp file, then rename) to prevent corruption.
* Missing files are treated as empty and return defaults on read.
* Updates use deep merge for nested objects like keyboardShortcuts and apiKeys.
*/
export class SettingsService { export class SettingsService {
private dataDir: string; private dataDir: string;
/**
* Create a new SettingsService instance
*
* @param dataDir - Absolute path to global data directory (e.g., ~/.automaker)
*/
constructor(dataDir: string) { constructor(dataDir: string) {
this.dataDir = dataDir; this.dataDir = dataDir;
} }
@@ -100,7 +118,13 @@ export class SettingsService {
// ============================================================================ // ============================================================================
/** /**
* Get global settings * Get global settings with defaults applied for any missing fields
*
* Reads from {dataDir}/settings.json. If file doesn't exist, returns defaults.
* Missing fields are filled in from DEFAULT_GLOBAL_SETTINGS for forward/backward
* compatibility during schema migrations.
*
* @returns Promise resolving to complete GlobalSettings object
*/ */
async getGlobalSettings(): Promise<GlobalSettings> { async getGlobalSettings(): Promise<GlobalSettings> {
const settingsPath = getGlobalSettingsPath(this.dataDir); const settingsPath = getGlobalSettingsPath(this.dataDir);
@@ -121,7 +145,13 @@ export class SettingsService {
} }
/** /**
* Update global settings (partial update) * Update global settings with partial changes
*
* Performs a deep merge: nested objects like keyboardShortcuts are merged,
* not replaced. Updates are written atomically. Creates dataDir if needed.
*
* @param updates - Partial GlobalSettings to merge (only provided fields are updated)
* @returns Promise resolving to complete updated GlobalSettings
*/ */
async updateGlobalSettings( async updateGlobalSettings(
updates: Partial<GlobalSettings> updates: Partial<GlobalSettings>
@@ -152,6 +182,10 @@ export class SettingsService {
/** /**
* Check if global settings file exists * Check if global settings file exists
*
* Used to determine if user has previously configured settings.
*
* @returns Promise resolving to true if {dataDir}/settings.json exists
*/ */
async hasGlobalSettings(): Promise<boolean> { async hasGlobalSettings(): Promise<boolean> {
const settingsPath = getGlobalSettingsPath(this.dataDir); const settingsPath = getGlobalSettingsPath(this.dataDir);
@@ -163,7 +197,13 @@ export class SettingsService {
// ============================================================================ // ============================================================================
/** /**
* Get credentials * Get credentials with defaults applied
*
* Reads from {dataDir}/credentials.json. If file doesn't exist, returns
* defaults (empty API keys). Used primarily by backend for API authentication.
* UI should use getMaskedCredentials() instead.
*
* @returns Promise resolving to complete Credentials object
*/ */
async getCredentials(): Promise<Credentials> { async getCredentials(): Promise<Credentials> {
const credentialsPath = getCredentialsPath(this.dataDir); const credentialsPath = getCredentialsPath(this.dataDir);
@@ -183,7 +223,14 @@ export class SettingsService {
} }
/** /**
* Update credentials (partial update) * Update credentials with partial changes
*
* Updates individual API keys. Uses deep merge for apiKeys object.
* Creates dataDir if needed. Credentials are written atomically.
* WARNING: Use only in secure contexts - keys are unencrypted.
*
* @param updates - Partial Credentials (usually just apiKeys)
* @returns Promise resolving to complete updated Credentials object
*/ */
async updateCredentials( async updateCredentials(
updates: Partial<Credentials> updates: Partial<Credentials>
@@ -213,7 +260,13 @@ export class SettingsService {
} }
/** /**
* Get masked credentials (for UI display - don't expose full keys) * Get masked credentials safe for UI display
*
* Returns API keys masked for security (first 4 and last 4 chars visible).
* Use this for showing credential status in UI without exposing full keys.
* Each key includes a 'configured' boolean and masked string representation.
*
* @returns Promise resolving to masked credentials object with each provider's status
*/ */
async getMaskedCredentials(): Promise<{ async getMaskedCredentials(): Promise<{
anthropic: { configured: boolean; masked: string }; anthropic: { configured: boolean; masked: string };
@@ -245,6 +298,10 @@ export class SettingsService {
/** /**
* Check if credentials file exists * Check if credentials file exists
*
* Used to determine if user has configured any API keys.
*
* @returns Promise resolving to true if {dataDir}/credentials.json exists
*/ */
async hasCredentials(): Promise<boolean> { async hasCredentials(): Promise<boolean> {
const credentialsPath = getCredentialsPath(this.dataDir); const credentialsPath = getCredentialsPath(this.dataDir);
@@ -256,7 +313,14 @@ export class SettingsService {
// ============================================================================ // ============================================================================
/** /**
* Get project settings * Get project-specific settings with defaults applied
*
* Reads from {projectPath}/.automaker/settings.json. If file doesn't exist,
* returns defaults. Project settings are optional - missing values fall back
* to global settings on the UI side.
*
* @param projectPath - Absolute path to project directory
* @returns Promise resolving to complete ProjectSettings object
*/ */
async getProjectSettings(projectPath: string): Promise<ProjectSettings> { async getProjectSettings(projectPath: string): Promise<ProjectSettings> {
const settingsPath = getProjectSettingsPath(projectPath); const settingsPath = getProjectSettingsPath(projectPath);
@@ -272,7 +336,14 @@ export class SettingsService {
} }
/** /**
* Update project settings (partial update) * Update project-specific settings with partial changes
*
* Performs a deep merge on boardBackground. Creates .automaker directory
* in project if needed. Updates are written atomically.
*
* @param projectPath - Absolute path to project directory
* @param updates - Partial ProjectSettings to merge
* @returns Promise resolving to complete updated ProjectSettings
*/ */
async updateProjectSettings( async updateProjectSettings(
projectPath: string, projectPath: string,
@@ -304,6 +375,9 @@ export class SettingsService {
/** /**
* Check if project settings file exists * Check if project settings file exists
*
* @param projectPath - Absolute path to project directory
* @returns Promise resolving to true if {projectPath}/.automaker/settings.json exists
*/ */
async hasProjectSettings(projectPath: string): Promise<boolean> { async hasProjectSettings(projectPath: string): Promise<boolean> {
const settingsPath = getProjectSettingsPath(projectPath); const settingsPath = getProjectSettingsPath(projectPath);
@@ -315,8 +389,15 @@ export class SettingsService {
// ============================================================================ // ============================================================================
/** /**
* Migrate settings from localStorage data * Migrate settings from localStorage to file-based storage
* This is called when the UI detects it has localStorage data but no settings files *
* Called during onboarding when UI detects localStorage data but no settings files.
* Extracts global settings, credentials, and per-project settings from various
* localStorage keys and writes them to the new file-based storage.
* Collects errors but continues on partial failures.
*
* @param localStorageData - Object containing localStorage key/value pairs to migrate
* @returns Promise resolving to migration result with success status and error list
*/ */
async migrateFromLocalStorage(localStorageData: { async migrateFromLocalStorage(localStorageData: {
"automaker-storage"?: string; "automaker-storage"?: string;
@@ -534,7 +615,12 @@ export class SettingsService {
} }
/** /**
* Get the DATA_DIR path (for debugging/info) * Get the data directory path
*
* Returns the absolute path to the directory where global settings and
* credentials are stored. Useful for logging, debugging, and validation.
*
* @returns Absolute path to data directory
*/ */
getDataDir(): string { getDataDir(): string {
return this.dataDir; return this.dataDir;

View File

@@ -1,8 +1,20 @@
/** /**
* Settings Types - Shared types for file-based settings storage * Settings Types - Shared types for file-based settings storage
*
* Defines the structure for global settings, credentials, and per-project settings
* that are persisted to disk in JSON format. These types are used by both the server
* (for file I/O via SettingsService) and the UI (for state management and sync).
*/ */
// Theme modes (matching UI ThemeMode type) /**
* ThemeMode - Available color themes for the UI
*
* Includes system theme and multiple color schemes:
* - System: Respects OS dark/light mode preference
* - Light/Dark: Basic light and dark variants
* - Color Schemes: Retro, Dracula, Nord, Monokai, Tokyo Night, Solarized, Gruvbox,
* Catppuccin, OneDark, Synthwave, Red, Cream, Sunset, Gray
*/
export type ThemeMode = export type ThemeMode =
| "light" | "light"
| "dark" | "dark"
@@ -22,184 +34,325 @@ export type ThemeMode =
| "sunset" | "sunset"
| "gray"; | "gray";
/** KanbanCardDetailLevel - Controls how much information is displayed on kanban cards */
export type KanbanCardDetailLevel = "minimal" | "standard" | "detailed"; export type KanbanCardDetailLevel = "minimal" | "standard" | "detailed";
/** AgentModel - Available Claude models for feature generation and planning */
export type AgentModel = "opus" | "sonnet" | "haiku"; export type AgentModel = "opus" | "sonnet" | "haiku";
/** PlanningMode - Planning levels for feature generation workflows */
export type PlanningMode = "skip" | "lite" | "spec" | "full"; export type PlanningMode = "skip" | "lite" | "spec" | "full";
/** ThinkingLevel - Extended thinking levels for Claude models (reasoning intensity) */
export type ThinkingLevel = "none" | "low" | "medium" | "high" | "ultrathink"; export type ThinkingLevel = "none" | "low" | "medium" | "high" | "ultrathink";
/** ModelProvider - AI model provider for credentials and API key management */
export type ModelProvider = "claude"; export type ModelProvider = "claude";
// Keyboard Shortcuts /**
* KeyboardShortcuts - User-configurable keyboard bindings for common actions
*
* Each property maps an action to a keyboard shortcut string
* (e.g., "Ctrl+K", "Alt+N", "Shift+P")
*/
export interface KeyboardShortcuts { export interface KeyboardShortcuts {
/** Open board view */
board: string; board: string;
/** Open agent panel */
agent: string; agent: string;
/** Open feature spec editor */
spec: string; spec: string;
/** Open context files panel */
context: string; context: string;
/** Open settings */
settings: string; settings: string;
/** Open AI profiles */
profiles: string; profiles: string;
/** Open terminal */
terminal: string; terminal: string;
/** Toggle sidebar visibility */
toggleSidebar: string; toggleSidebar: string;
/** Add new feature */
addFeature: string; addFeature: string;
/** Add context file */
addContextFile: string; addContextFile: string;
/** Start next feature generation */
startNext: string; startNext: string;
/** Create new chat session */
newSession: string; newSession: string;
/** Open project picker */
openProject: string; openProject: string;
/** Open project picker (alternate) */
projectPicker: string; projectPicker: string;
/** Cycle to previous project */
cyclePrevProject: string; cyclePrevProject: string;
/** Cycle to next project */
cycleNextProject: string; cycleNextProject: string;
/** Add new AI profile */
addProfile: string; addProfile: string;
/** Split terminal right */
splitTerminalRight: string; splitTerminalRight: string;
/** Split terminal down */
splitTerminalDown: string; splitTerminalDown: string;
/** Close current terminal */
closeTerminal: string; closeTerminal: string;
} }
// AI Profile /**
* AIProfile - Configuration for an AI model with specific parameters
*
* Profiles can be built-in defaults or user-created. They define which model to use,
* thinking level, and other parameters for feature generation tasks.
*/
export interface AIProfile { export interface AIProfile {
/** Unique identifier for the profile */
id: string; id: string;
/** Display name for the profile */
name: string; name: string;
/** User-friendly description */
description: string; description: string;
/** Which Claude model to use (opus, sonnet, haiku) */
model: AgentModel; model: AgentModel;
/** Extended thinking level for reasoning-based tasks */
thinkingLevel: ThinkingLevel; thinkingLevel: ThinkingLevel;
/** Provider (currently only "claude") */
provider: ModelProvider; provider: ModelProvider;
/** Whether this is a built-in default profile */
isBuiltIn: boolean; isBuiltIn: boolean;
/** Optional icon identifier or emoji */
icon?: string; icon?: string;
} }
// Project reference (minimal info stored in global settings) /**
* ProjectRef - Minimal reference to a project stored in global settings
*
* Used for the projects list and project history. Full project data is loaded separately.
*/
export interface ProjectRef { export interface ProjectRef {
/** Unique identifier */
id: string; id: string;
/** Display name */
name: string; name: string;
/** Absolute filesystem path to project directory */
path: string; path: string;
/** ISO timestamp of last time project was opened */
lastOpened?: string; lastOpened?: string;
/** Project-specific theme override (or undefined to use global) */
theme?: string; theme?: string;
} }
// Trashed project reference /**
* TrashedProjectRef - Reference to a project in the trash/recycle bin
*
* Extends ProjectRef with deletion metadata. User can permanently delete or restore.
*/
export interface TrashedProjectRef extends ProjectRef { export interface TrashedProjectRef extends ProjectRef {
/** ISO timestamp when project was moved to trash */
trashedAt: string; trashedAt: string;
/** Whether project folder was deleted from disk */
deletedFromDisk?: boolean; deletedFromDisk?: boolean;
} }
// Chat session (minimal info, full content can be loaded separately) /**
* ChatSessionRef - Minimal reference to a chat session
*
* Used for session lists and history. Full session content is stored separately.
*/
export interface ChatSessionRef { export interface ChatSessionRef {
/** Unique session identifier */
id: string; id: string;
/** User-given or AI-generated title */
title: string; title: string;
/** Project that session belongs to */
projectId: string; projectId: string;
/** ISO timestamp of creation */
createdAt: string; createdAt: string;
/** ISO timestamp of last message */
updatedAt: string; updatedAt: string;
/** Whether session is archived */
archived: boolean; archived: boolean;
} }
/** /**
* Global Settings - stored in {DATA_DIR}/settings.json * GlobalSettings - User preferences and state stored globally in {DATA_DIR}/settings.json
*
* This is the main settings file that persists user preferences across sessions.
* Includes theme, UI state, feature defaults, keyboard shortcuts, AI profiles, and projects.
* Format: JSON with version field for migration support.
*/ */
export interface GlobalSettings { export interface GlobalSettings {
/** Version number for schema migration */
version: number; version: number;
// Theme // Theme Configuration
/** Currently selected theme */
theme: ThemeMode; theme: ThemeMode;
// UI State // UI State Preferences
/** Whether sidebar is currently open */
sidebarOpen: boolean; sidebarOpen: boolean;
/** Whether chat history panel is open */
chatHistoryOpen: boolean; chatHistoryOpen: boolean;
/** How much detail to show on kanban cards */
kanbanCardDetailLevel: KanbanCardDetailLevel; kanbanCardDetailLevel: KanbanCardDetailLevel;
// Feature Defaults // Feature Generation Defaults
/** Max features to generate concurrently */
maxConcurrency: number; maxConcurrency: number;
/** Default: skip tests during feature generation */
defaultSkipTests: boolean; defaultSkipTests: boolean;
/** Default: enable dependency blocking */
enableDependencyBlocking: boolean; enableDependencyBlocking: boolean;
/** Default: use git worktrees for feature branches */
useWorktrees: boolean; useWorktrees: boolean;
/** Default: only show AI profiles (hide other settings) */
showProfilesOnly: boolean; showProfilesOnly: boolean;
/** Default: planning approach (skip/lite/spec/full) */
defaultPlanningMode: PlanningMode; defaultPlanningMode: PlanningMode;
/** Default: require manual approval before generating */
defaultRequirePlanApproval: boolean; defaultRequirePlanApproval: boolean;
/** ID of currently selected AI profile (null = use built-in) */
defaultAIProfileId: string | null; defaultAIProfileId: string | null;
// Audio // Audio Preferences
/** Mute completion notification sound */
muteDoneSound: boolean; muteDoneSound: boolean;
// Enhancement // AI Model Selection
/** Which model to use for feature name/description enhancement */
enhancementModel: AgentModel; enhancementModel: AgentModel;
// Keyboard Shortcuts // Input Configuration
/** User's keyboard shortcut bindings */
keyboardShortcuts: KeyboardShortcuts; keyboardShortcuts: KeyboardShortcuts;
// AI Profiles // AI Profiles
/** User-created AI profiles */
aiProfiles: AIProfile[]; aiProfiles: AIProfile[];
// Projects // Project Management
/** List of active projects */
projects: ProjectRef[]; projects: ProjectRef[];
/** Projects in trash/recycle bin */
trashedProjects: TrashedProjectRef[]; trashedProjects: TrashedProjectRef[];
/** History of recently opened project IDs */
projectHistory: string[]; projectHistory: string[];
/** Current position in project history for navigation */
projectHistoryIndex: number; projectHistoryIndex: number;
// UI Preferences (previously in direct localStorage) // File Browser and UI Preferences
/** Last directory opened in file picker */
lastProjectDir?: string; lastProjectDir?: string;
/** Recently accessed folders for quick access */
recentFolders: string[]; recentFolders: string[];
/** Whether worktree panel is collapsed in current view */
worktreePanelCollapsed: boolean; worktreePanelCollapsed: boolean;
// Session tracking (per-project, keyed by project path) // Session Tracking
/** Maps project path -> last selected session ID in that project */
lastSelectedSessionByProject: Record<string, string>; lastSelectedSessionByProject: Record<string, string>;
} }
/** /**
* Credentials - stored in {DATA_DIR}/credentials.json * Credentials - API keys stored in {DATA_DIR}/credentials.json
*
* Sensitive data stored separately from general settings.
* Keys should never be exposed in UI or logs.
*/ */
export interface Credentials { export interface Credentials {
/** Version number for schema migration */
version: number; version: number;
/** API keys for various providers */
apiKeys: { apiKeys: {
/** Anthropic Claude API key */
anthropic: string; anthropic: string;
/** Google API key (for embeddings or other services) */
google: string; google: string;
/** OpenAI API key (for compatibility or alternative providers) */
openai: string; openai: string;
}; };
} }
/** /**
* Board Background Settings * BoardBackgroundSettings - Kanban board appearance customization
*
* Controls background images, opacity, borders, and visual effects for the board.
*/ */
export interface BoardBackgroundSettings { export interface BoardBackgroundSettings {
/** Path to background image file (null = no image) */
imagePath: string | null; imagePath: string | null;
/** Version/timestamp of image for cache busting */
imageVersion?: number; imageVersion?: number;
/** Opacity of cards (0-1) */
cardOpacity: number; cardOpacity: number;
/** Opacity of columns (0-1) */
columnOpacity: number; columnOpacity: number;
/** Show border around columns */
columnBorderEnabled: boolean; columnBorderEnabled: boolean;
/** Apply glassmorphism effect to cards */
cardGlassmorphism: boolean; cardGlassmorphism: boolean;
/** Show border around cards */
cardBorderEnabled: boolean; cardBorderEnabled: boolean;
/** Opacity of card borders (0-1) */
cardBorderOpacity: number; cardBorderOpacity: number;
/** Hide scrollbar in board view */
hideScrollbar: boolean; hideScrollbar: boolean;
} }
/** /**
* Worktree Info * WorktreeInfo - Information about a git worktree
*
* Tracks worktree location, branch, and dirty state for project management.
*/ */
export interface WorktreeInfo { export interface WorktreeInfo {
/** Absolute path to worktree directory */
path: string; path: string;
/** Branch checked out in this worktree */
branch: string; branch: string;
/** Whether this is the main worktree */
isMain: boolean; isMain: boolean;
/** Whether worktree has uncommitted changes */
hasChanges?: boolean; hasChanges?: boolean;
/** Number of files with changes */
changedFilesCount?: number; changedFilesCount?: number;
} }
/** /**
* Per-Project Settings - stored in {projectPath}/.automaker/settings.json * ProjectSettings - Project-specific overrides stored in {projectPath}/.automaker/settings.json
*
* Allows per-project customization without affecting global settings.
* All fields are optional - missing values fall back to global settings.
*/ */
export interface ProjectSettings { export interface ProjectSettings {
/** Version number for schema migration */
version: number; version: number;
// Theme override (null = use global) // Theme Configuration (project-specific override)
/** Project theme (undefined = use global setting) */
theme?: ThemeMode; theme?: ThemeMode;
// Worktree settings // Worktree Management
/** Project-specific worktree preference override */
useWorktrees?: boolean; useWorktrees?: boolean;
/** Current worktree being used in this project */
currentWorktree?: { path: string | null; branch: string }; currentWorktree?: { path: string | null; branch: string };
/** List of worktrees available in this project */
worktrees?: WorktreeInfo[]; worktrees?: WorktreeInfo[];
// Board background // Board Customization
/** Project-specific board background settings */
boardBackground?: BoardBackgroundSettings; boardBackground?: BoardBackgroundSettings;
// Last selected session // Session Tracking
/** Last chat session selected in this project */
lastSelectedSessionId?: string; lastSelectedSessionId?: string;
} }
// Default values /**
* Default values and constants
*/
/** Default keyboard shortcut bindings */
export const DEFAULT_KEYBOARD_SHORTCUTS: KeyboardShortcuts = { export const DEFAULT_KEYBOARD_SHORTCUTS: KeyboardShortcuts = {
board: "K", board: "K",
agent: "A", agent: "A",
@@ -223,6 +376,7 @@ export const DEFAULT_KEYBOARD_SHORTCUTS: KeyboardShortcuts = {
closeTerminal: "Alt+W", closeTerminal: "Alt+W",
}; };
/** Default global settings used when no settings file exists */
export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = { export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
version: 1, version: 1,
theme: "dark", theme: "dark",
@@ -251,6 +405,7 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
lastSelectedSessionByProject: {}, lastSelectedSessionByProject: {},
}; };
/** Default credentials (empty strings - user must provide API keys) */
export const DEFAULT_CREDENTIALS: Credentials = { export const DEFAULT_CREDENTIALS: Credentials = {
version: 1, version: 1,
apiKeys: { apiKeys: {
@@ -260,10 +415,14 @@ export const DEFAULT_CREDENTIALS: Credentials = {
}, },
}; };
/** Default project settings (empty - all settings are optional and fall back to global) */
export const DEFAULT_PROJECT_SETTINGS: ProjectSettings = { export const DEFAULT_PROJECT_SETTINGS: ProjectSettings = {
version: 1, version: 1,
}; };
/** Current version of the global settings schema */
export const SETTINGS_VERSION = 1; export const SETTINGS_VERSION = 1;
/** Current version of the credentials schema */
export const CREDENTIALS_VERSION = 1; export const CREDENTIALS_VERSION = 1;
/** Current version of the project settings schema */
export const PROJECT_SETTINGS_VERSION = 1; export const PROJECT_SETTINGS_VERSION = 1;

View File

@@ -1,27 +1,44 @@
/** /**
* Settings Migration Hook * Settings Migration Hook and Sync Functions
* *
* This hook handles migrating settings from localStorage to file-based storage. * Handles migrating user settings from localStorage to persistent file-based storage
* It runs on app startup and: * on app startup. Also provides utility functions for syncing individual setting
* 1. Checks if server has settings files * categories to the server.
* 2. If not, migrates localStorage data to server
* 3. Clears old localStorage keys after successful migration
* *
* This approach keeps localStorage as a fast cache while ensuring * Migration flow:
* settings are persisted to files that survive app updates. * 1. useSettingsMigration() hook checks server for existing settings files
* 2. If none exist, collects localStorage data and sends to /api/settings/migrate
* 3. After successful migration, clears deprecated localStorage keys
* 4. Maintains automaker-storage in localStorage as fast cache for Zustand
*
* Sync functions for incremental updates:
* - syncSettingsToServer: Writes global settings to file
* - syncCredentialsToServer: Writes API keys to file
* - syncProjectSettingsToServer: Writes project-specific overrides
*/ */
import { useEffect, useState, useRef } from "react"; import { useEffect, useState, useRef } from "react";
import { getHttpApiClient } from "@/lib/http-api-client"; import { getHttpApiClient } from "@/lib/http-api-client";
import { isElectron } from "@/lib/electron"; import { isElectron } from "@/lib/electron";
/**
* State returned by useSettingsMigration hook
*/
interface MigrationState { interface MigrationState {
/** Whether migration check has completed */
checked: boolean; checked: boolean;
/** Whether migration actually occurred */
migrated: boolean; migrated: boolean;
/** Error message if migration failed (null if success/no-op) */
error: string | null; error: string | null;
} }
// localStorage keys to migrate /**
* localStorage keys that may contain settings to migrate
*
* These keys are collected and sent to the server for migration.
* The automaker-storage key is handled specially as it's still used by Zustand.
*/
const LOCALSTORAGE_KEYS = [ const LOCALSTORAGE_KEYS = [
"automaker-storage", "automaker-storage",
"automaker-setup", "automaker-setup",
@@ -30,19 +47,34 @@ const LOCALSTORAGE_KEYS = [
"automaker:lastProjectDir", "automaker:lastProjectDir",
] as const; ] as const;
// Keys to clear after migration (not automaker-storage as it's still used by Zustand) /**
* localStorage keys to remove after successful migration
*
* automaker-storage is intentionally NOT in this list because Zustand still uses it
* as a cache. These other keys have been migrated and are no longer needed.
*/
const KEYS_TO_CLEAR_AFTER_MIGRATION = [ const KEYS_TO_CLEAR_AFTER_MIGRATION = [
"worktree-panel-collapsed", "worktree-panel-collapsed",
"file-browser-recent-folders", "file-browser-recent-folders",
"automaker:lastProjectDir", "automaker:lastProjectDir",
// Legacy keys // Legacy keys from older versions
"automaker_projects", "automaker_projects",
"automaker_current_project", "automaker_current_project",
"automaker_trashed_projects", "automaker_trashed_projects",
] as const; ] as const;
/** /**
* Hook to handle settings migration from localStorage to file-based storage * React hook to handle settings migration from localStorage to file-based storage
*
* Runs automatically once on component mount. Returns state indicating whether
* migration check is complete, whether migration occurred, and any errors.
*
* Only runs in Electron mode (isElectron() must be true). Web mode uses different
* storage mechanisms.
*
* The hook uses a ref to ensure it only runs once despite multiple mounts.
*
* @returns MigrationState with checked, migrated, and error fields
*/ */
export function useSettingsMigration(): MigrationState { export function useSettingsMigration(): MigrationState {
const [state, setState] = useState<MigrationState>({ const [state, setState] = useState<MigrationState>({
@@ -154,8 +186,17 @@ export function useSettingsMigration(): MigrationState {
} }
/** /**
* Sync current settings to the server * Sync current global settings to file-based server storage
* Call this when important settings change *
* Reads the current Zustand state from localStorage and sends all global settings
* to the server to be written to {dataDir}/settings.json.
*
* Call this when important global settings change (theme, UI preferences, profiles, etc.)
* Safe to call from store subscribers or change handlers.
*
* Only functions in Electron mode. Returns false if not in Electron or on error.
*
* @returns Promise resolving to true if sync succeeded, false otherwise
*/ */
export async function syncSettingsToServer(): Promise<boolean> { export async function syncSettingsToServer(): Promise<boolean> {
if (!isElectron()) return false; if (!isElectron()) return false;
@@ -205,8 +246,18 @@ export async function syncSettingsToServer(): Promise<boolean> {
} }
/** /**
* Sync credentials to the server * Sync API credentials to file-based server storage
* Call this when API keys change *
* Sends API keys (partial update supported) to the server to be written to
* {dataDir}/credentials.json. Credentials are kept separate from settings for security.
*
* Call this when API keys are added or updated in settings UI.
* Only requires providing the keys that have changed.
*
* Only functions in Electron mode. Returns false if not in Electron or on error.
*
* @param apiKeys - Partial credential object with optional anthropic, google, openai keys
* @returns Promise resolving to true if sync succeeded, false otherwise
*/ */
export async function syncCredentialsToServer(apiKeys: { export async function syncCredentialsToServer(apiKeys: {
anthropic?: string; anthropic?: string;
@@ -226,8 +277,20 @@ export async function syncCredentialsToServer(apiKeys: {
} }
/** /**
* Sync project settings to the server * Sync project-specific settings to file-based server storage
* Call this when project-specific settings change *
* Sends project settings (theme, worktree config, board customization) to the server
* to be written to {projectPath}/.automaker/settings.json.
*
* These settings override global settings for specific projects.
* Supports partial updates - only include fields that have changed.
*
* Call this when project settings are modified in the board or settings UI.
* Only functions in Electron mode. Returns false if not in Electron or on error.
*
* @param projectPath - Absolute path to project directory
* @param updates - Partial ProjectSettings with optional theme, worktree, and board settings
* @returns Promise resolving to true if sync succeeded, false otherwise
*/ */
export async function syncProjectSettingsToServer( export async function syncProjectSettingsToServer(
projectPath: string, projectPath: string,