mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
Merge remote-tracking branch 'upstream/v0.12.0rc' into fix/light-mode-agent-output
This commit is contained in:
3
.github/actions/setup-project/action.yml
vendored
3
.github/actions/setup-project/action.yml
vendored
@@ -41,7 +41,8 @@ runs:
|
||||
# Use npm install instead of npm ci to correctly resolve platform-specific
|
||||
# optional dependencies (e.g., @tailwindcss/oxide, lightningcss binaries)
|
||||
# Skip scripts to avoid electron-builder install-app-deps which uses too much memory
|
||||
run: npm install --ignore-scripts
|
||||
# Use --force to allow platform-specific dev dependencies like dmg-license on non-darwin platforms
|
||||
run: npm install --ignore-scripts --force
|
||||
|
||||
- name: Install Linux native bindings
|
||||
shell: bash
|
||||
|
||||
2
.github/workflows/format-check.yml
vendored
2
.github/workflows/format-check.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
cache-dependency-path: package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install --ignore-scripts
|
||||
run: npm install --ignore-scripts --force
|
||||
|
||||
- name: Check formatting
|
||||
run: npm run format:check
|
||||
|
||||
13
.github/workflows/release.yml
vendored
13
.github/workflows/release.yml
vendored
@@ -35,6 +35,11 @@ jobs:
|
||||
with:
|
||||
check-lockfile: 'true'
|
||||
|
||||
- name: Install RPM build tools (Linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
shell: bash
|
||||
run: sudo apt-get update && sudo apt-get install -y rpm
|
||||
|
||||
- name: Build Electron app (macOS)
|
||||
if: matrix.os == 'macos-latest'
|
||||
shell: bash
|
||||
@@ -73,7 +78,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-builds
|
||||
path: apps/ui/release/*.{AppImage,deb}
|
||||
path: apps/ui/release/*.{AppImage,deb,rpm}
|
||||
retention-days: 30
|
||||
|
||||
upload:
|
||||
@@ -104,8 +109,8 @@ jobs:
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
artifacts/macos-builds/*
|
||||
artifacts/windows-builds/*
|
||||
artifacts/linux-builds/*
|
||||
artifacts/macos-builds/*.{dmg,zip,blockmap}
|
||||
artifacts/windows-builds/*.{exe,blockmap}
|
||||
artifacts/linux-builds/*.{AppImage,deb,rpm,blockmap}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
21
README.md
21
README.md
@@ -214,11 +214,30 @@ npm run build:electron
|
||||
# Platform-specific builds
|
||||
npm run build:electron:mac # macOS (DMG + ZIP, x64 + arm64)
|
||||
npm run build:electron:win # Windows (NSIS installer, x64)
|
||||
npm run build:electron:linux # Linux (AppImage + DEB, x64)
|
||||
npm run build:electron:linux # Linux (AppImage + DEB + RPM, x64)
|
||||
|
||||
# Output directory: apps/ui/release/
|
||||
```
|
||||
|
||||
**Linux Distribution Packages:**
|
||||
|
||||
- **AppImage**: Universal format, works on any Linux distribution
|
||||
- **DEB**: Ubuntu, Debian, Linux Mint, Pop!\_OS
|
||||
- **RPM**: Fedora, RHEL, Rocky Linux, AlmaLinux, openSUSE
|
||||
|
||||
**Installing on Fedora/RHEL:**
|
||||
|
||||
```bash
|
||||
# Download the RPM package
|
||||
wget https://github.com/AutoMaker-Org/automaker/releases/latest/download/Automaker-<version>-x86_64.rpm
|
||||
|
||||
# Install with dnf (Fedora)
|
||||
sudo dnf install ./Automaker-<version>-x86_64.rpm
|
||||
|
||||
# Or with yum (RHEL/CentOS)
|
||||
sudo yum localinstall ./Automaker-<version>-x86_64.rpm
|
||||
```
|
||||
|
||||
#### Docker Deployment
|
||||
|
||||
Docker provides the most secure way to run Automaker by isolating it from your host filesystem.
|
||||
|
||||
@@ -142,6 +142,8 @@ if (process.env.AUTOMAKER_HIDE_API_KEY !== 'true') {
|
||||
║ ${API_KEY}
|
||||
║ ║
|
||||
║ In Electron mode, authentication is handled automatically. ║
|
||||
║ ║
|
||||
║ 💡 Tip: Set AUTOMAKER_API_KEY env var to use a fixed key for dev ║
|
||||
╚═══════════════════════════════════════════════════════════════════════╝
|
||||
`);
|
||||
} else {
|
||||
|
||||
@@ -78,7 +78,7 @@ export async function loadBacklogPlan(projectPath: string): Promise<StoredBacklo
|
||||
const filePath = getBacklogPlanPath(projectPath);
|
||||
const raw = await secureFs.readFile(filePath, 'utf-8');
|
||||
const parsed = JSON.parse(raw as string) as StoredBacklogPlan;
|
||||
if (!parsed?.result?.changes) {
|
||||
if (!Array.isArray(parsed?.result?.changes)) {
|
||||
return null;
|
||||
}
|
||||
return parsed;
|
||||
|
||||
@@ -17,7 +17,13 @@ import { resolvePhaseModel } from '@automaker/model-resolver';
|
||||
import { FeatureLoader } from '../../services/feature-loader.js';
|
||||
import { ProviderFactory } from '../../providers/provider-factory.js';
|
||||
import { extractJsonWithArray } from '../../lib/json-extractor.js';
|
||||
import { logger, setRunningState, getErrorMessage, saveBacklogPlan } from './common.js';
|
||||
import {
|
||||
logger,
|
||||
setRunningState,
|
||||
setRunningDetails,
|
||||
getErrorMessage,
|
||||
saveBacklogPlan,
|
||||
} from './common.js';
|
||||
import type { SettingsService } from '../../services/settings-service.js';
|
||||
import { getAutoLoadClaudeMdSetting, getPromptCustomization } from '../../lib/settings-helpers.js';
|
||||
|
||||
@@ -225,5 +231,6 @@ ${userPrompt}`;
|
||||
throw error;
|
||||
} finally {
|
||||
setRunningState(false, null);
|
||||
setRunningDetails(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,12 +147,21 @@ export function createApplyHandler() {
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the plan before responding
|
||||
try {
|
||||
await clearBacklogPlan(projectPath);
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`[BacklogPlan] Failed to clear backlog plan after apply:`,
|
||||
getErrorMessage(error)
|
||||
);
|
||||
// Don't throw - operation succeeded, just cleanup failed
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
appliedChanges,
|
||||
});
|
||||
|
||||
await clearBacklogPlan(projectPath);
|
||||
} catch (error) {
|
||||
logError(error, 'Apply backlog plan failed');
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
|
||||
@@ -63,6 +63,7 @@ export function createGenerateHandler(events: EventEmitter, settingsService?: Se
|
||||
})
|
||||
.finally(() => {
|
||||
setRunningState(false, null);
|
||||
setRunningDetails(null);
|
||||
});
|
||||
|
||||
res.json({ success: true });
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import { getAbortController, setRunningState, getErrorMessage, logError } from '../common.js';
|
||||
import {
|
||||
getAbortController,
|
||||
setRunningState,
|
||||
setRunningDetails,
|
||||
getErrorMessage,
|
||||
logError,
|
||||
} from '../common.js';
|
||||
|
||||
export function createStopHandler() {
|
||||
return async (_req: Request, res: Response): Promise<void> => {
|
||||
@@ -12,6 +18,7 @@ export function createStopHandler() {
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
setRunningState(false, null);
|
||||
setRunningDetails(null);
|
||||
}
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
|
||||
@@ -16,10 +16,27 @@ import { isGitRepo } from '@automaker/git-utils';
|
||||
import { getErrorMessage, logError, normalizePath, execEnv, isGhCliAvailable } from '../common.js';
|
||||
import { readAllWorktreeMetadata, type WorktreePRInfo } from '../../../lib/worktree-metadata.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import {
|
||||
checkGitHubRemote,
|
||||
type GitHubRemoteStatus,
|
||||
} from '../../github/routes/check-github-remote.js';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
const logger = createLogger('Worktree');
|
||||
|
||||
/**
|
||||
* Cache for GitHub remote status per project path.
|
||||
* This prevents repeated "no git remotes found" warnings when polling
|
||||
* projects that don't have a GitHub remote configured.
|
||||
*/
|
||||
interface GitHubRemoteCacheEntry {
|
||||
status: GitHubRemoteStatus;
|
||||
checkedAt: number;
|
||||
}
|
||||
|
||||
const githubRemoteCache = new Map<string, GitHubRemoteCacheEntry>();
|
||||
const GITHUB_REMOTE_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
interface WorktreeInfo {
|
||||
path: string;
|
||||
branch: string;
|
||||
@@ -121,23 +138,63 @@ async function scanWorktreesDirectory(
|
||||
return discovered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached GitHub remote status for a project, or check and cache it.
|
||||
* Returns null if gh CLI is not available.
|
||||
*/
|
||||
async function getGitHubRemoteStatus(projectPath: string): Promise<GitHubRemoteStatus | null> {
|
||||
// Check if gh CLI is available first
|
||||
const ghAvailable = await isGhCliAvailable();
|
||||
if (!ghAvailable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const cached = githubRemoteCache.get(projectPath);
|
||||
|
||||
// Return cached result if still valid
|
||||
if (cached && now - cached.checkedAt < GITHUB_REMOTE_CACHE_TTL_MS) {
|
||||
return cached.status;
|
||||
}
|
||||
|
||||
// Check GitHub remote and cache the result
|
||||
const status = await checkGitHubRemote(projectPath);
|
||||
githubRemoteCache.set(projectPath, {
|
||||
status,
|
||||
checkedAt: Date.now(),
|
||||
});
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch open PRs from GitHub and create a map of branch name to PR info.
|
||||
* This allows detecting PRs that were created outside the app.
|
||||
*
|
||||
* Uses cached GitHub remote status to avoid repeated warnings when the
|
||||
* project doesn't have a GitHub remote configured.
|
||||
*/
|
||||
async function fetchGitHubPRs(projectPath: string): Promise<Map<string, WorktreePRInfo>> {
|
||||
const prMap = new Map<string, WorktreePRInfo>();
|
||||
|
||||
try {
|
||||
// Check if gh CLI is available
|
||||
const ghAvailable = await isGhCliAvailable();
|
||||
if (!ghAvailable) {
|
||||
// Check GitHub remote status (uses cache to avoid repeated warnings)
|
||||
const remoteStatus = await getGitHubRemoteStatus(projectPath);
|
||||
|
||||
// If gh CLI not available or no GitHub remote, return empty silently
|
||||
if (!remoteStatus || !remoteStatus.hasGitHubRemote) {
|
||||
return prMap;
|
||||
}
|
||||
|
||||
// Use -R flag with owner/repo for more reliable PR fetching
|
||||
const repoFlag =
|
||||
remoteStatus.owner && remoteStatus.repo
|
||||
? `-R ${remoteStatus.owner}/${remoteStatus.repo}`
|
||||
: '';
|
||||
|
||||
// Fetch open PRs from GitHub
|
||||
const { stdout } = await execAsync(
|
||||
'gh pr list --state open --json number,title,url,state,headRefName,createdAt --limit 1000',
|
||||
`gh pr list ${repoFlag} --state open --json number,title,url,state,headRefName,createdAt --limit 1000`,
|
||||
{ cwd: projectPath, env: execEnv, timeout: 15000 }
|
||||
);
|
||||
|
||||
@@ -170,9 +227,10 @@ async function fetchGitHubPRs(projectPath: string): Promise<Map<string, Worktree
|
||||
export function createListHandler() {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, includeDetails } = req.body as {
|
||||
const { projectPath, includeDetails, forceRefreshGitHub } = req.body as {
|
||||
projectPath: string;
|
||||
includeDetails?: boolean;
|
||||
forceRefreshGitHub?: boolean;
|
||||
};
|
||||
|
||||
if (!projectPath) {
|
||||
@@ -180,6 +238,12 @@ export function createListHandler() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear GitHub remote cache if force refresh requested
|
||||
// This allows users to re-check for GitHub remote after adding one
|
||||
if (forceRefreshGitHub) {
|
||||
githubRemoteCache.delete(projectPath);
|
||||
}
|
||||
|
||||
if (!(await isGitRepo(projectPath))) {
|
||||
res.json({ success: true, worktrees: [] });
|
||||
return;
|
||||
|
||||
@@ -220,12 +220,34 @@
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "rpm",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": "Development",
|
||||
"icon": "public/logo_larger.png",
|
||||
"maintainer": "webdevcody@gmail.com",
|
||||
"executableName": "automaker"
|
||||
"executableName": "automaker",
|
||||
"description": "An autonomous AI development studio that helps you build software faster using AI-powered agents",
|
||||
"synopsis": "AI-powered autonomous development studio"
|
||||
},
|
||||
"rpm": {
|
||||
"depends": [
|
||||
"gtk3",
|
||||
"libnotify",
|
||||
"nss",
|
||||
"libXScrnSaver",
|
||||
"libXtst",
|
||||
"xdg-utils",
|
||||
"at-spi2-core",
|
||||
"libuuid"
|
||||
],
|
||||
"compression": "xz",
|
||||
"vendor": "AutoMaker Team"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
AlertCircle,
|
||||
ListChecks,
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn, generateUUID } from '@/lib/utils';
|
||||
|
||||
const logger = createLogger('AnalysisView');
|
||||
|
||||
@@ -638,7 +638,7 @@ ${Object.entries(projectAnalysis.filesByExtension)
|
||||
|
||||
for (const detectedFeature of detectedFeatures) {
|
||||
await api.features.create(currentProject.path, {
|
||||
id: crypto.randomUUID(),
|
||||
id: generateUUID(),
|
||||
category: detectedFeature.category,
|
||||
description: detectedFeature.description,
|
||||
status: 'backlog',
|
||||
|
||||
@@ -39,7 +39,10 @@ export function useWorktrees({
|
||||
logger.warn('Worktree API not available');
|
||||
return;
|
||||
}
|
||||
const result = await api.worktree.listAll(projectPath, true);
|
||||
// Pass forceRefreshGitHub when this is a manual refresh (not silent polling)
|
||||
// This clears the GitHub remote cache so users can re-detect after adding a remote
|
||||
const forceRefreshGitHub = !silent;
|
||||
const result = await api.worktree.listAll(projectPath, true, forceRefreshGitHub);
|
||||
if (result.success && result.worktrees) {
|
||||
setWorktrees(result.worktrees);
|
||||
setWorktreesInStore(projectPath, result.worktrees);
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||
import { LoadingState } from '@/components/ui/loading-state';
|
||||
import { ErrorState } from '@/components/ui/error-state';
|
||||
import { cn, pathsEqual } from '@/lib/utils';
|
||||
import { cn, pathsEqual, generateUUID } from '@/lib/utils';
|
||||
import { toast } from 'sonner';
|
||||
import { useGithubIssues, useIssueValidation, useIssuesFilter } from './github-issues-view/hooks';
|
||||
import { IssueRow, IssueDetailPanel, IssuesListHeader } from './github-issues-view/components';
|
||||
@@ -137,7 +137,7 @@ export function GitHubIssuesView() {
|
||||
.join('\n');
|
||||
|
||||
const feature = {
|
||||
id: `issue-${issue.number}-${crypto.randomUUID()}`,
|
||||
id: `issue-${issue.number}-${generateUUID()}`,
|
||||
title: issue.title,
|
||||
description,
|
||||
category: 'From GitHub',
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Bot, Send, User, Loader2, Sparkles, FileText, ArrowLeft, CheckCircle } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn, generateUUID } from '@/lib/utils';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { Markdown } from '@/components/ui/markdown';
|
||||
import { useFileBrowser } from '@/contexts/file-browser-context';
|
||||
@@ -345,7 +345,7 @@ export function InterviewView() {
|
||||
|
||||
// Create initial feature in the features folder
|
||||
const initialFeature: Feature = {
|
||||
id: crypto.randomUUID(),
|
||||
id: generateUUID(),
|
||||
category: 'Core',
|
||||
description: 'Initial project setup',
|
||||
status: 'backlog' as const,
|
||||
|
||||
@@ -28,6 +28,7 @@ import type {
|
||||
EventHookHttpAction,
|
||||
} from '@automaker/types';
|
||||
import { EVENT_HOOK_TRIGGER_LABELS } from '@automaker/types';
|
||||
import { generateUUID } from '@/lib/utils';
|
||||
|
||||
interface EventHookDialogProps {
|
||||
open: boolean;
|
||||
@@ -109,7 +110,7 @@ export function EventHookDialog({ open, onOpenChange, editingHook, onSave }: Eve
|
||||
|
||||
const handleSave = () => {
|
||||
const hook: EventHook = {
|
||||
id: editingHook?.id || crypto.randomUUID(),
|
||||
id: editingHook?.id || generateUUID(),
|
||||
name: name.trim() || undefined,
|
||||
trigger,
|
||||
enabled: editingHook?.enabled ?? true,
|
||||
|
||||
@@ -166,6 +166,7 @@ export function PhaseModelSelector({
|
||||
codexModelsLoading,
|
||||
fetchCodexModels,
|
||||
dynamicOpencodeModels,
|
||||
enabledDynamicModelIds,
|
||||
opencodeModelsLoading,
|
||||
fetchOpencodeModels,
|
||||
disabledProviders,
|
||||
@@ -383,13 +384,16 @@ export function PhaseModelSelector({
|
||||
const staticModels = [...OPENCODE_MODELS];
|
||||
|
||||
// Add dynamic models (convert ModelDefinition to ModelOption)
|
||||
const dynamicModelOptions: ModelOption[] = dynamicOpencodeModels.map((model) => ({
|
||||
id: model.id,
|
||||
label: model.name,
|
||||
description: model.description,
|
||||
badge: model.tier === 'premium' ? 'Premium' : model.tier === 'basic' ? 'Free' : undefined,
|
||||
provider: 'opencode' as const,
|
||||
}));
|
||||
// Only include dynamic models that are enabled by the user
|
||||
const dynamicModelOptions: ModelOption[] = dynamicOpencodeModels
|
||||
.filter((model) => enabledDynamicModelIds.includes(model.id))
|
||||
.map((model) => ({
|
||||
id: model.id,
|
||||
label: model.name,
|
||||
description: model.description,
|
||||
badge: model.tier === 'premium' ? 'Premium' : model.tier === 'basic' ? 'Free' : undefined,
|
||||
provider: 'opencode' as const,
|
||||
}));
|
||||
|
||||
// Merge, avoiding duplicates (static models take precedence for same ID)
|
||||
// In practice, static and dynamic IDs don't overlap
|
||||
@@ -397,7 +401,7 @@ export function PhaseModelSelector({
|
||||
const uniqueDynamic = dynamicModelOptions.filter((m) => !staticIds.has(m.id));
|
||||
|
||||
return [...staticModels, ...uniqueDynamic];
|
||||
}, [dynamicOpencodeModels]);
|
||||
}, [dynamicOpencodeModels, enabledDynamicModelIds]);
|
||||
|
||||
// Group models (filtering out disabled providers)
|
||||
const { favorites, claude, cursor, codex, opencode } = useMemo(() => {
|
||||
|
||||
@@ -611,16 +611,16 @@ export function OpencodeModelConfiguration({
|
||||
Dynamic
|
||||
</Badge>
|
||||
</div>
|
||||
{models.length > 0 && (
|
||||
{filteredModels.length > 0 && (
|
||||
<div className={OPENCODE_SELECT_ALL_CONTAINER_CLASS}>
|
||||
<Checkbox
|
||||
checked={getSelectionState(
|
||||
models.map((model) => model.id),
|
||||
filteredModels.map((model) => model.id),
|
||||
enabledDynamicModelIds
|
||||
)}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleProviderDynamicModels(
|
||||
models.map((model) => model.id),
|
||||
filteredModels.map((model) => model.id),
|
||||
checked
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1596,10 +1596,15 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
return { success: true, worktrees: [] };
|
||||
},
|
||||
|
||||
listAll: async (projectPath: string, includeDetails?: boolean) => {
|
||||
listAll: async (
|
||||
projectPath: string,
|
||||
includeDetails?: boolean,
|
||||
forceRefreshGitHub?: boolean
|
||||
) => {
|
||||
console.log('[Mock] Listing all worktrees:', {
|
||||
projectPath,
|
||||
includeDetails,
|
||||
forceRefreshGitHub,
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -1724,8 +1724,8 @@ export class HttpApiClient implements ElectronAPI {
|
||||
getStatus: (projectPath: string, featureId: string) =>
|
||||
this.post('/api/worktree/status', { projectPath, featureId }),
|
||||
list: (projectPath: string) => this.post('/api/worktree/list', { projectPath }),
|
||||
listAll: (projectPath: string, includeDetails?: boolean) =>
|
||||
this.post('/api/worktree/list', { projectPath, includeDetails }),
|
||||
listAll: (projectPath: string, includeDetails?: boolean, forceRefreshGitHub?: boolean) =>
|
||||
this.post('/api/worktree/list', { projectPath, includeDetails, forceRefreshGitHub }),
|
||||
create: (projectPath: string, branchName: string, baseBranch?: string) =>
|
||||
this.post('/api/worktree/create', {
|
||||
projectPath,
|
||||
|
||||
@@ -124,3 +124,39 @@ export const isMac =
|
||||
: typeof navigator !== 'undefined' &&
|
||||
(/Mac/.test(navigator.userAgent) ||
|
||||
(navigator.platform ? navigator.platform.toLowerCase().includes('mac') : false));
|
||||
|
||||
/**
|
||||
* Generate a UUID v4 string.
|
||||
*
|
||||
* Uses crypto.randomUUID() when available (secure contexts: HTTPS or localhost).
|
||||
* Falls back to crypto.getRandomValues() for non-secure contexts (e.g., Docker via HTTP).
|
||||
*
|
||||
* @returns A RFC 4122 compliant UUID v4 string (e.g., "550e8400-e29b-41d4-a716-446655440000")
|
||||
*/
|
||||
export function generateUUID(): string {
|
||||
// Use native randomUUID if available (secure contexts: HTTPS or localhost)
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
// Fallback using crypto.getRandomValues() (works in all modern browsers, including non-secure contexts)
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {
|
||||
const bytes = new Uint8Array(16);
|
||||
crypto.getRandomValues(bytes);
|
||||
|
||||
// Set version (4) and variant (RFC 4122) bits
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant RFC 4122
|
||||
|
||||
// Convert to hex string with proper UUID format
|
||||
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
||||
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
||||
}
|
||||
|
||||
// Last resort fallback using Math.random() - less secure but ensures functionality
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||
const r = (Math.random() * 16) | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
3
apps/ui/src/types/electron.d.ts
vendored
3
apps/ui/src/types/electron.d.ts
vendored
@@ -705,7 +705,8 @@ export interface WorktreeAPI {
|
||||
// List all worktrees with details (for worktree selector)
|
||||
listAll: (
|
||||
projectPath: string,
|
||||
includeDetails?: boolean
|
||||
includeDetails?: boolean,
|
||||
forceRefreshGitHub?: boolean
|
||||
) => Promise<{
|
||||
success: boolean;
|
||||
worktrees?: Array<{
|
||||
|
||||
@@ -130,8 +130,8 @@ test.describe('Feature Manual Review Flow', () => {
|
||||
await page.waitForTimeout(300);
|
||||
}
|
||||
|
||||
// Verify we're on the correct project (project name appears in sidebar button)
|
||||
await expect(page.getByRole('button', { name: new RegExp(projectName) })).toBeVisible({
|
||||
// Verify we're on the correct project (project switcher button shows project name)
|
||||
await expect(page.getByTestId(`project-switcher-project-${projectName}`)).toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
|
||||
@@ -77,8 +77,8 @@ test.describe('Project Creation', () => {
|
||||
}
|
||||
|
||||
// Wait for project to be set as current and visible on the page
|
||||
// The project name appears in the sidebar project selector button
|
||||
await expect(page.getByRole('button', { name: new RegExp(projectName) })).toBeVisible({
|
||||
// The project name appears in the project switcher button
|
||||
await expect(page.getByTestId(`project-switcher-project-${projectName}`)).toBeVisible({
|
||||
timeout: 15000,
|
||||
});
|
||||
|
||||
|
||||
@@ -156,9 +156,9 @@ test.describe('Open Project', () => {
|
||||
}
|
||||
|
||||
// Wait for a project to be set as current and visible on the page
|
||||
// The project name appears in the sidebar project selector button
|
||||
// The project name appears in the project switcher button
|
||||
if (targetProjectName) {
|
||||
await expect(page.getByRole('button', { name: new RegExp(targetProjectName) })).toBeVisible({
|
||||
await expect(page.getByTestId(`project-switcher-project-${targetProjectName}`)).toBeVisible({
|
||||
timeout: 15000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -74,19 +74,17 @@ services:
|
||||
command:
|
||||
- -c
|
||||
- |
|
||||
# Fix permissions on node_modules (created as root by Docker volume)
|
||||
echo 'Fixing node_modules permissions...'
|
||||
chown -R automaker:automaker /app/node_modules 2>/dev/null || true
|
||||
# Install as root to avoid permission issues with named volumes
|
||||
# Use --force to skip platform-specific devDependencies (dmg-license is macOS-only)
|
||||
echo 'Installing dependencies...' &&
|
||||
npm ci --legacy-peer-deps --force &&
|
||||
echo 'Building shared packages...' &&
|
||||
npm run build:packages &&
|
||||
|
||||
# Run the rest as automaker user
|
||||
exec gosu automaker sh -c "
|
||||
echo 'Installing dependencies...' &&
|
||||
npm install &&
|
||||
echo 'Building shared packages...' &&
|
||||
npm run build:packages &&
|
||||
echo 'Starting server in development mode...' &&
|
||||
npm run _dev:server
|
||||
"
|
||||
# Fix permissions and start server as automaker user
|
||||
chown -R automaker:automaker /app/node_modules &&
|
||||
echo 'Starting server in development mode...' &&
|
||||
exec gosu automaker npm run _dev:server
|
||||
healthcheck:
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost:3008/api/health']
|
||||
interval: 10s
|
||||
|
||||
@@ -75,19 +75,17 @@ services:
|
||||
command:
|
||||
- -c
|
||||
- |
|
||||
# Fix permissions on node_modules (created as root by Docker volume)
|
||||
echo 'Fixing node_modules permissions...'
|
||||
chown -R automaker:automaker /app/node_modules 2>/dev/null || true
|
||||
# Install as root to avoid permission issues with named volumes
|
||||
# Use --force to skip platform-specific devDependencies (dmg-license is macOS-only)
|
||||
echo 'Installing dependencies...' &&
|
||||
npm ci --legacy-peer-deps --force &&
|
||||
echo 'Building shared packages...' &&
|
||||
npm run build:packages &&
|
||||
|
||||
# Run the rest as automaker user
|
||||
exec gosu automaker sh -c "
|
||||
echo 'Installing dependencies...' &&
|
||||
npm install &&
|
||||
echo 'Building shared packages...' &&
|
||||
npm run build:packages &&
|
||||
echo 'Starting server in development mode...' &&
|
||||
npm run _dev:server
|
||||
"
|
||||
# Fix permissions and start server as automaker user
|
||||
chown -R automaker:automaker /app/node_modules &&
|
||||
echo 'Starting server in development mode...' &&
|
||||
exec gosu automaker npm run _dev:server
|
||||
healthcheck:
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost:3008/api/health']
|
||||
interval: 10s
|
||||
|
||||
485
docs/install-fedora.md
Normal file
485
docs/install-fedora.md
Normal file
@@ -0,0 +1,485 @@
|
||||
# Installing Automaker on Fedora/RHEL
|
||||
|
||||
This guide covers installation of Automaker on Fedora, RHEL, Rocky Linux, AlmaLinux, and other RPM-based distributions.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Automaker requires:
|
||||
|
||||
- **64-bit x86_64 architecture**
|
||||
- **Fedora 39+** or **RHEL 9+** (earlier versions may work but not officially supported)
|
||||
- **4GB RAM minimum**, 8GB recommended
|
||||
- **~300MB disk space** for installation
|
||||
- **Internet connection** for installation and Claude API access
|
||||
|
||||
### Authentication
|
||||
|
||||
You'll need one of the following:
|
||||
|
||||
- **Claude CLI** (recommended) - `claude login`
|
||||
- **API key** - Set `ANTHROPIC_API_KEY` environment variable
|
||||
|
||||
See main [README.md authentication section](../README.md#authentication) for details.
|
||||
|
||||
## Installation
|
||||
|
||||
### Option 1: Download and Install from GitHub
|
||||
|
||||
1. Visit [GitHub Releases](https://github.com/AutoMaker-Org/automaker/releases)
|
||||
2. Find the latest release and download the `.rpm` file:
|
||||
- Download: `Automaker-<version>-x86_64.rpm`
|
||||
|
||||
3. Install using dnf (Fedora):
|
||||
|
||||
```bash
|
||||
sudo dnf install ./Automaker-<version>-x86_64.rpm
|
||||
```
|
||||
|
||||
Or using yum (RHEL/CentOS):
|
||||
|
||||
```bash
|
||||
sudo yum localinstall ./Automaker-<version>-x86_64.rpm
|
||||
```
|
||||
|
||||
### Option 2: Install Directly from URL
|
||||
|
||||
Install from GitHub releases URL without downloading first. Visit [releases page](https://github.com/AutoMaker-Org/automaker/releases) to find the latest version.
|
||||
|
||||
**Fedora:**
|
||||
|
||||
```bash
|
||||
# Replace v0.11.0 with the actual latest version
|
||||
sudo dnf install https://github.com/AutoMaker-Org/automaker/releases/download/v0.11.0/Automaker-0.11.0-x86_64.rpm
|
||||
```
|
||||
|
||||
**RHEL/CentOS:**
|
||||
|
||||
```bash
|
||||
# Replace v0.11.0 with the actual latest version
|
||||
sudo yum install https://github.com/AutoMaker-Org/automaker/releases/download/v0.11.0/Automaker-0.11.0-x86_64.rpm
|
||||
```
|
||||
|
||||
## Running Automaker
|
||||
|
||||
After successful installation, launch Automaker:
|
||||
|
||||
### From Application Menu
|
||||
|
||||
- Open Activities/Applications
|
||||
- Search for "Automaker"
|
||||
- Click to launch
|
||||
|
||||
### From Terminal
|
||||
|
||||
```bash
|
||||
automaker
|
||||
```
|
||||
|
||||
## System Requirements & Capabilities
|
||||
|
||||
### Hardware Requirements
|
||||
|
||||
| Component | Minimum | Recommended |
|
||||
| ------------ | ----------------- | ----------- |
|
||||
| CPU | Modern multi-core | 4+ cores |
|
||||
| RAM | 4GB | 8GB+ |
|
||||
| Disk | 300MB | 1GB+ |
|
||||
| Architecture | x86_64 | x86_64 |
|
||||
|
||||
### Required Dependencies
|
||||
|
||||
The RPM package automatically installs these dependencies:
|
||||
|
||||
```
|
||||
gtk3 - GTK+ GUI library
|
||||
libnotify - Desktop notification library
|
||||
nss - Network Security Services
|
||||
libXScrnSaver - X11 screensaver library
|
||||
libXtst - X11 testing library
|
||||
xdg-utils - XDG standards utilities
|
||||
at-spi2-core - Accessibility library
|
||||
libuuid - UUID library
|
||||
```
|
||||
|
||||
Most of these are pre-installed on typical Fedora/RHEL systems.
|
||||
|
||||
### Optional Dependencies
|
||||
|
||||
For development (source builds only):
|
||||
|
||||
- Node.js 22+
|
||||
- npm 10+
|
||||
|
||||
The packaged application includes its own Electron runtime and does not require system Node.js.
|
||||
|
||||
## Supported Distributions
|
||||
|
||||
**Officially Tested:**
|
||||
|
||||
- Fedora 39, 40 (latest)
|
||||
- Rocky Linux 9
|
||||
- AlmaLinux 9
|
||||
|
||||
**Should Work:**
|
||||
|
||||
- CentOS Stream 9+
|
||||
- openSUSE Leap/Tumbleweed (with compatibility layer)
|
||||
- RHEL 9+
|
||||
|
||||
**Not Supported:**
|
||||
|
||||
- RHEL 8 (glibc 2.28 too old, requires Node.js 22)
|
||||
- CentOS 7 and earlier
|
||||
- Fedora versions older than 39
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Set authentication via environment variable:
|
||||
|
||||
```bash
|
||||
export ANTHROPIC_API_KEY=sk-ant-...
|
||||
automaker
|
||||
```
|
||||
|
||||
Or create `~/.config/automaker/.env`:
|
||||
|
||||
```
|
||||
ANTHROPIC_API_KEY=sk-ant-...
|
||||
```
|
||||
|
||||
### Configuration Directory
|
||||
|
||||
Automaker stores configuration and cache in:
|
||||
|
||||
```
|
||||
~/.automaker/ # Project-specific data
|
||||
~/.config/automaker/ # Application configuration
|
||||
~/.cache/automaker/ # Cache and temporary files
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Application Won't Start
|
||||
|
||||
**Check installation:**
|
||||
|
||||
```bash
|
||||
rpm -qi automaker
|
||||
rpm -V automaker
|
||||
```
|
||||
|
||||
**Verify desktop file:**
|
||||
|
||||
```bash
|
||||
cat /usr/share/applications/automaker.desktop
|
||||
```
|
||||
|
||||
**Run from terminal for error output:**
|
||||
|
||||
```bash
|
||||
automaker
|
||||
```
|
||||
|
||||
### Missing Dependencies
|
||||
|
||||
If dependencies fail to install automatically:
|
||||
|
||||
**Fedora:**
|
||||
|
||||
```bash
|
||||
sudo dnf install gtk3 libnotify nss libXScrnSaver libXtst xdg-utils at-spi2-core libuuid
|
||||
```
|
||||
|
||||
**RHEL/CentOS (enable EPEL first if needed):**
|
||||
|
||||
```bash
|
||||
sudo dnf install epel-release
|
||||
sudo dnf install gtk3 libnotify nss libXScrnSaver libXtst xdg-utils at-spi2-core libuuid
|
||||
```
|
||||
|
||||
### SELinux Denials
|
||||
|
||||
If Automaker fails on SELinux-enforced systems:
|
||||
|
||||
**Temporary workaround (testing):**
|
||||
|
||||
```bash
|
||||
# Set SELinux to permissive mode
|
||||
sudo setenforce 0
|
||||
|
||||
# Run Automaker
|
||||
automaker
|
||||
|
||||
# Check for denials
|
||||
sudo ausearch -m avc -ts recent | grep automaker
|
||||
|
||||
# Re-enable SELinux
|
||||
sudo setenforce 1
|
||||
```
|
||||
|
||||
**Permanent fix (not recommended for production):**
|
||||
Create custom SELinux policy based on ausearch output. For support, see [GitHub Issues](https://github.com/AutoMaker-Org/automaker/issues).
|
||||
|
||||
### Port Conflicts
|
||||
|
||||
Automaker uses port 3008 for the internal server. If port is already in use:
|
||||
|
||||
**Find process using port 3008:**
|
||||
|
||||
```bash
|
||||
sudo ss -tlnp | grep 3008
|
||||
# or
|
||||
lsof -i :3008
|
||||
```
|
||||
|
||||
**Kill conflicting process (if safe):**
|
||||
|
||||
```bash
|
||||
sudo kill -9 <PID>
|
||||
```
|
||||
|
||||
Or configure Automaker to use different port (see Configuration section).
|
||||
|
||||
### Firewall Issues
|
||||
|
||||
On Fedora with firewalld enabled:
|
||||
|
||||
```bash
|
||||
# Allow internal traffic (local development only)
|
||||
sudo firewall-cmd --add-port=3008/tcp
|
||||
sudo firewall-cmd --permanent --add-port=3008/tcp
|
||||
```
|
||||
|
||||
### GPU/Acceleration
|
||||
|
||||
Automaker uses Chromium for rendering. GPU acceleration should work automatically on supported systems.
|
||||
|
||||
**Check acceleration:**
|
||||
|
||||
- Look for "GPU acceleration" status in application settings
|
||||
- Verify drivers: `lspci | grep VGA`
|
||||
|
||||
**Disable acceleration if issues occur:**
|
||||
|
||||
```bash
|
||||
DISABLE_GPU_ACCELERATION=1 automaker
|
||||
```
|
||||
|
||||
### Terminal/Worktree Issues
|
||||
|
||||
If terminal emulator fails or git worktree operations hang:
|
||||
|
||||
1. Check disk space: `df -h`
|
||||
2. Verify git installation: `git --version`
|
||||
3. Check /tmp permissions: `ls -la /tmp`
|
||||
4. File a GitHub issue with error output
|
||||
|
||||
### Unresponsive GUI
|
||||
|
||||
If the application freezes:
|
||||
|
||||
1. Wait 30 seconds (AI operations may be processing)
|
||||
2. Check process: `ps aux | grep automaker`
|
||||
3. Force quit if necessary: `killall automaker`
|
||||
4. Check system resources: `free -h`, `top`
|
||||
|
||||
### Network Issues
|
||||
|
||||
If Claude API calls fail:
|
||||
|
||||
```bash
|
||||
# Test internet connectivity
|
||||
ping -c 3 api.anthropic.com
|
||||
|
||||
# Test API access
|
||||
curl -I https://api.anthropic.com
|
||||
|
||||
# Verify API key is set (without exposing the value)
|
||||
[ -n "$ANTHROPIC_API_KEY" ] && echo "API key is set" || echo "API key is NOT set"
|
||||
```
|
||||
|
||||
## Uninstallation
|
||||
|
||||
### Remove Application
|
||||
|
||||
**Fedora:**
|
||||
|
||||
```bash
|
||||
sudo dnf remove automaker
|
||||
```
|
||||
|
||||
**RHEL/CentOS:**
|
||||
|
||||
```bash
|
||||
sudo yum remove automaker
|
||||
```
|
||||
|
||||
### Clean Configuration (Optional)
|
||||
|
||||
Remove all user data and configuration:
|
||||
|
||||
```bash
|
||||
# Remove project-specific data
|
||||
rm -rf ~/.automaker
|
||||
|
||||
# Remove application configuration
|
||||
rm -rf ~/.config/automaker
|
||||
|
||||
# Remove cache
|
||||
rm -rf ~/.cache/automaker
|
||||
```
|
||||
|
||||
**Warning:** This removes all saved projects and settings. Ensure you have backups if needed.
|
||||
|
||||
## Building from Source
|
||||
|
||||
To build Automaker from source on Fedora/RHEL:
|
||||
|
||||
**Prerequisites:**
|
||||
|
||||
```bash
|
||||
# Fedora
|
||||
sudo dnf install nodejs npm git
|
||||
|
||||
# RHEL (enable EPEL first)
|
||||
sudo dnf install epel-release
|
||||
sudo dnf install nodejs npm git
|
||||
```
|
||||
|
||||
**Build steps:**
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://github.com/AutoMaker-Org/automaker.git
|
||||
cd automaker
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Build packages
|
||||
npm run build:packages
|
||||
|
||||
# Build Linux packages
|
||||
npm run build:electron:linux
|
||||
|
||||
# Packages in: apps/ui/release/
|
||||
ls apps/ui/release/*.rpm
|
||||
```
|
||||
|
||||
See main [README.md](../README.md) for detailed build instructions.
|
||||
|
||||
## Updating Automaker
|
||||
|
||||
**Automatic Updates:**
|
||||
Automaker checks for updates on startup. Install available updates through notifications.
|
||||
|
||||
**Manual Update:**
|
||||
|
||||
```bash
|
||||
# Fedora
|
||||
sudo dnf update automaker
|
||||
|
||||
# RHEL/CentOS
|
||||
sudo yum update automaker
|
||||
|
||||
# Or reinstall latest release
|
||||
sudo dnf remove automaker
|
||||
|
||||
# Download the latest .rpm from releases page
|
||||
# https://github.com/AutoMaker-Org/automaker/releases
|
||||
# Then reinstall with:
|
||||
# sudo dnf install ./Automaker-<VERSION>-x86_64.rpm
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
|
||||
### Resources
|
||||
|
||||
- [Main README](../README.md) - Project overview
|
||||
- [CONTRIBUTING.md](../CONTRIBUTING.md) - Contributing guide
|
||||
- [GitHub Issues](https://github.com/AutoMaker-Org/automaker/issues) - Bug reports & feature requests
|
||||
- [Discussions](https://github.com/AutoMaker-Org/automaker/discussions) - Questions & community
|
||||
|
||||
### Reporting Issues
|
||||
|
||||
When reporting Fedora/RHEL issues, include:
|
||||
|
||||
```bash
|
||||
# System information
|
||||
lsb_release -a
|
||||
uname -m
|
||||
|
||||
# Automaker version
|
||||
rpm -qi automaker
|
||||
|
||||
# Error output (run from terminal)
|
||||
automaker 2>&1 | tee automaker.log
|
||||
|
||||
# SELinux status
|
||||
getenforce
|
||||
|
||||
# Relevant system logs
|
||||
sudo journalctl -xeu automaker.service (if systemd service exists)
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. **Use SSD**: Faster than spinning disk, significantly improves performance
|
||||
2. **Close unnecessary applications**: Free up RAM for AI agent processing
|
||||
3. **Disable GPU acceleration if glitchy**: Set `DISABLE_GPU_ACCELERATION=1`
|
||||
4. **Keep system updated**: `sudo dnf update`
|
||||
5. **Use latest Fedora/RHEL**: Newer versions have better Electron support
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### API Key Security
|
||||
|
||||
Never commit API keys to version control:
|
||||
|
||||
```bash
|
||||
# Good: Use environment variable
|
||||
export ANTHROPIC_API_KEY=sk-ant-...
|
||||
|
||||
# Good: Use .env file (not in git)
|
||||
echo "ANTHROPIC_API_KEY=sk-ant-..." > ~/.config/automaker/.env
|
||||
|
||||
# Bad: Hardcoded in files
|
||||
ANTHROPIC_API_KEY="sk-ant-..." (in any tracked file)
|
||||
```
|
||||
|
||||
### SELinux Security
|
||||
|
||||
Running with SELinux disabled (`setenforce 0`) reduces security. Create custom policy:
|
||||
|
||||
1. Generate policy from audit logs: `ausearch -m avc -ts recent | grep automaker`
|
||||
2. Use selinux-policy tools to create module
|
||||
3. Install and test module
|
||||
4. Keep SELinux enforcing
|
||||
|
||||
### File Permissions
|
||||
|
||||
Ensure configuration files are readable by user only:
|
||||
|
||||
```bash
|
||||
chmod 600 ~/.config/automaker/.env
|
||||
chmod 700 ~/.automaker/
|
||||
chmod 700 ~/.config/automaker/
|
||||
```
|
||||
|
||||
## Known Limitations
|
||||
|
||||
1. **Single display support**: Multi-monitor setups may have cursor synchronization issues
|
||||
2. **X11 only**: Wayland support limited (runs under XWayland)
|
||||
3. **No native systemd service**: Manual launcher or desktop file shortcut
|
||||
4. **ARM/ARM64**: Not supported, x86_64 only
|
||||
|
||||
## Contributing
|
||||
|
||||
Found an issue or want to improve Fedora support? See [CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2026-01-16
|
||||
**Tested On**: Fedora 40, Rocky Linux 9, AlmaLinux 9
|
||||
@@ -125,11 +125,14 @@ export function isOpencodeModel(model: string | undefined | null): boolean {
|
||||
// - github-copilot/gpt-4o
|
||||
// - google/gemini-2.5-pro
|
||||
// - xai/grok-3
|
||||
// Pattern: provider-id/model-name (must have exactly one / and not be a URL)
|
||||
// - openrouter/qwen/qwen3-14b:free (model names can contain / or :)
|
||||
// Pattern: provider-id/model-name (at least one /, not a URL)
|
||||
if (model.includes('/') && !model.includes('://')) {
|
||||
const parts = model.split('/');
|
||||
// Valid dynamic model format: provider/model-name (exactly 2 parts)
|
||||
if (parts.length === 2 && parts[0].length > 0 && parts[1].length > 0) {
|
||||
const slashIndex = model.indexOf('/');
|
||||
const providerId = model.substring(0, slashIndex);
|
||||
const modelName = model.substring(slashIndex + 1);
|
||||
// Valid dynamic model format: provider-id/model-name (both parts non-empty)
|
||||
if (providerId.length > 0 && modelName.length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
31
package-lock.json
generated
31
package-lock.json
generated
@@ -18,6 +18,7 @@
|
||||
"tree-kill": "1.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dmg-license": "^1.0.11",
|
||||
"husky": "9.1.7",
|
||||
"lint-staged": "16.2.7",
|
||||
"prettier": "3.7.4",
|
||||
@@ -6132,7 +6133,6 @@
|
||||
"integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"xmlbuilder": ">=11.0.1"
|
||||
@@ -6214,8 +6214,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz",
|
||||
"integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.18.1",
|
||||
@@ -7240,7 +7239,6 @@
|
||||
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
@@ -7293,7 +7291,6 @@
|
||||
"integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -8038,7 +8035,6 @@
|
||||
"integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"slice-ansi": "^3.0.0",
|
||||
"string-width": "^4.2.0"
|
||||
@@ -8314,8 +8310,7 @@
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
@@ -8336,7 +8331,6 @@
|
||||
"integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"buffer": "^5.1.0"
|
||||
}
|
||||
@@ -8800,7 +8794,6 @@
|
||||
"integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
@@ -9693,8 +9686,7 @@
|
||||
"engines": [
|
||||
"node >=0.6.0"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
@@ -10658,7 +10650,6 @@
|
||||
"integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
@@ -11283,6 +11274,7 @@
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
@@ -11304,6 +11296,7 @@
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
@@ -11346,6 +11339,7 @@
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
@@ -11367,6 +11361,7 @@
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
@@ -11388,6 +11383,7 @@
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
@@ -11409,6 +11405,7 @@
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
@@ -11430,6 +11427,7 @@
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
@@ -11451,6 +11449,7 @@
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
@@ -11472,6 +11471,7 @@
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
@@ -13078,8 +13078,7 @@
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz",
|
||||
"integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-api-version": {
|
||||
"version": "0.2.1",
|
||||
@@ -14596,7 +14595,6 @@
|
||||
"integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"astral-regex": "^2.0.0",
|
||||
@@ -15713,7 +15711,6 @@
|
||||
"integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0",
|
||||
"core-util-is": "1.0.2",
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
"tree-kill": "1.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dmg-license": "^1.0.11",
|
||||
"husky": "9.1.7",
|
||||
"lint-staged": "16.2.7",
|
||||
"prettier": "3.7.4",
|
||||
|
||||
@@ -37,11 +37,11 @@ DEFAULT_SERVER_PORT=3008
|
||||
WEB_PORT=$DEFAULT_WEB_PORT
|
||||
SERVER_PORT=$DEFAULT_SERVER_PORT
|
||||
|
||||
# Extract VERSION from package.json (using node for reliable JSON parsing)
|
||||
# Extract VERSION from apps/ui/package.json (the actual app version, not monorepo version)
|
||||
if command -v node &> /dev/null; then
|
||||
VERSION="v$(node -p "require('$SCRIPT_DIR/package.json').version" 2>/dev/null || echo "0.0.0")"
|
||||
VERSION="v$(node -p "require('$SCRIPT_DIR/apps/ui/package.json').version" 2>/dev/null || echo "0.11.0")"
|
||||
else
|
||||
VERSION=$(grep '"version"' "$SCRIPT_DIR/package.json" | head -1 | sed 's/.*"version"[^"]*"\([^"]*\)".*/v\1/')
|
||||
VERSION=$(grep '"version"' "$SCRIPT_DIR/apps/ui/package.json" 2>/dev/null | head -1 | sed 's/.*"version"[^"]*"\([^"]*\)".*/v\1/' || echo "v0.11.0")
|
||||
fi
|
||||
|
||||
# ANSI Color codes (256-color palette)
|
||||
@@ -200,6 +200,8 @@ check_required_commands() {
|
||||
fi
|
||||
}
|
||||
|
||||
DOCKER_CMD="docker"
|
||||
|
||||
check_docker() {
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo "${C_RED}Error:${RESET} Docker is not installed or not in PATH"
|
||||
@@ -207,12 +209,22 @@ check_docker() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! docker info &> /dev/null; then
|
||||
echo "${C_RED}Error:${RESET} Docker daemon is not running"
|
||||
echo "Please start Docker and try again"
|
||||
return 1
|
||||
if ! docker info &> /dev/null 2>&1; then
|
||||
if sg docker -c "docker info" &> /dev/null 2>&1; then
|
||||
DOCKER_CMD="sg docker -c"
|
||||
else
|
||||
echo "${C_RED}Error:${RESET} Docker daemon is not running or not accessible"
|
||||
echo ""
|
||||
echo "To fix, run:"
|
||||
echo " sudo usermod -aG docker \$USER"
|
||||
echo ""
|
||||
echo "Then either log out and back in, or run:"
|
||||
echo " newgrp docker"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
export DOCKER_CMD
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -291,7 +303,11 @@ check_running_containers() {
|
||||
local running_containers=""
|
||||
|
||||
# Get list of running automaker containers
|
||||
running_containers=$(docker ps --filter "name=automaker-dev" --format "{{.Names}}" 2>/dev/null | tr '\n' ' ')
|
||||
if [ "$DOCKER_CMD" = "sg docker -c" ]; then
|
||||
running_containers=$(sg docker -c "docker ps --filter 'name=automaker-dev' --format '{{{{Names}}}}'" 2>/dev/null | tr '\n' ' ' || true)
|
||||
else
|
||||
running_containers=$($DOCKER_CMD ps --filter "name=automaker-dev" --format "{{.Names}}" 2>/dev/null | tr '\n' ' ' || true)
|
||||
fi
|
||||
|
||||
if [ -n "$running_containers" ] && [ "$running_containers" != " " ]; then
|
||||
get_term_size
|
||||
@@ -319,9 +335,13 @@ check_running_containers() {
|
||||
[sS]|[sS][tT][oO][pP])
|
||||
echo ""
|
||||
center_print "Stopping existing containers..." "$C_YELLOW"
|
||||
docker compose -f "$compose_file" down 2>/dev/null || true
|
||||
# Also try stopping any orphaned containers
|
||||
docker ps --filter "name=automaker-dev" -q 2>/dev/null | xargs -r docker stop 2>/dev/null || true
|
||||
if [ "$DOCKER_CMD" = "sg docker -c" ]; then
|
||||
sg docker -c "docker compose -f '$compose_file' down" 2>/dev/null || true
|
||||
sg docker -c "docker ps --filter 'name=automaker-dev' -q" 2>/dev/null | xargs -r sg docker -c "docker stop" 2>/dev/null || true
|
||||
else
|
||||
$DOCKER_CMD compose -f "$compose_file" down 2>/dev/null || true
|
||||
$DOCKER_CMD ps --filter "name=automaker-dev" -q 2>/dev/null | xargs -r $DOCKER_CMD stop 2>/dev/null || true
|
||||
fi
|
||||
center_print "✓ Containers stopped" "$C_GREEN"
|
||||
echo ""
|
||||
return 0 # Continue with fresh start
|
||||
@@ -329,7 +349,11 @@ check_running_containers() {
|
||||
[rR]|[rR][eE][sS][tT][aA][rR][tT])
|
||||
echo ""
|
||||
center_print "Stopping and rebuilding containers..." "$C_YELLOW"
|
||||
docker compose -f "$compose_file" down 2>/dev/null || true
|
||||
if [ "$DOCKER_CMD" = "sg docker -c" ]; then
|
||||
sg docker -c "docker compose -f '$compose_file' down" 2>/dev/null || true
|
||||
else
|
||||
$DOCKER_CMD compose -f "$compose_file" down 2>/dev/null || true
|
||||
fi
|
||||
center_print "✓ Ready to rebuild" "$C_GREEN"
|
||||
echo ""
|
||||
return 0 # Continue with rebuild
|
||||
@@ -1170,10 +1194,18 @@ case $MODE in
|
||||
center_print "API: http://localhost:$DEFAULT_SERVER_PORT" "$C_GREEN"
|
||||
center_print "Press Ctrl+C to detach" "$C_MUTE"
|
||||
echo ""
|
||||
if [ -f "docker-compose.override.yml" ]; then
|
||||
docker compose -f docker-compose.dev.yml -f docker-compose.override.yml logs -f
|
||||
if [ "$DOCKER_CMD" = "sg docker -c" ]; then
|
||||
if [ -f "docker-compose.override.yml" ]; then
|
||||
sg docker -c "docker compose -f 'docker-compose.dev.yml' -f 'docker-compose.override.yml' logs -f"
|
||||
else
|
||||
sg docker -c "docker compose -f 'docker-compose.dev.yml' logs -f"
|
||||
fi
|
||||
else
|
||||
docker compose -f docker-compose.dev.yml logs -f
|
||||
if [ -f "docker-compose.override.yml" ]; then
|
||||
$DOCKER_CMD compose -f docker-compose.dev.yml -f docker-compose.override.yml logs -f
|
||||
else
|
||||
$DOCKER_CMD compose -f docker-compose.dev.yml logs -f
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo ""
|
||||
@@ -1192,10 +1224,18 @@ case $MODE in
|
||||
echo ""
|
||||
center_print "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" "$C_GRAY"
|
||||
echo ""
|
||||
if [ -f "docker-compose.override.yml" ]; then
|
||||
docker compose -f docker-compose.dev.yml -f docker-compose.override.yml up --build
|
||||
if [ "$DOCKER_CMD" = "sg docker -c" ]; then
|
||||
if [ -f "docker-compose.override.yml" ]; then
|
||||
sg docker -c "docker compose -f 'docker-compose.dev.yml' -f 'docker-compose.override.yml' up --build"
|
||||
else
|
||||
sg docker -c "docker compose -f 'docker-compose.dev.yml' up --build"
|
||||
fi
|
||||
else
|
||||
docker compose -f docker-compose.dev.yml up --build
|
||||
if [ -f "docker-compose.override.yml" ]; then
|
||||
$DOCKER_CMD compose -f docker-compose.dev.yml -f docker-compose.override.yml up --build
|
||||
else
|
||||
$DOCKER_CMD compose -f docker-compose.dev.yml up --build
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
@@ -1235,10 +1275,18 @@ case $MODE in
|
||||
else
|
||||
center_print "Starting Docker server container..." "$C_MUTE"
|
||||
echo ""
|
||||
if [ -f "docker-compose.override.yml" ]; then
|
||||
docker compose -f docker-compose.dev-server.yml -f docker-compose.override.yml up --build &
|
||||
if [ "$DOCKER_CMD" = "sg docker -c" ]; then
|
||||
if [ -f "docker-compose.override.yml" ]; then
|
||||
sg docker -c "docker compose -f 'docker-compose.dev-server.yml' -f 'docker-compose.override.yml' up --build" &
|
||||
else
|
||||
sg docker -c "docker compose -f 'docker-compose.dev-server.yml' up --build" &
|
||||
fi
|
||||
else
|
||||
docker compose -f docker-compose.dev-server.yml up --build &
|
||||
if [ -f "docker-compose.override.yml" ]; then
|
||||
$DOCKER_CMD compose -f docker-compose.dev-server.yml -f docker-compose.override.yml up --build &
|
||||
else
|
||||
$DOCKER_CMD compose -f docker-compose.dev-server.yml up --build &
|
||||
fi
|
||||
fi
|
||||
DOCKER_PID=$!
|
||||
fi
|
||||
@@ -1284,7 +1332,11 @@ case $MODE in
|
||||
echo ""
|
||||
center_print "Shutting down Docker container..." "$C_MUTE"
|
||||
[ -n "$DOCKER_PID" ] && kill $DOCKER_PID 2>/dev/null || true
|
||||
docker compose -f docker-compose.dev-server.yml down 2>/dev/null || true
|
||||
if [ "$DOCKER_CMD" = "sg docker -c" ]; then
|
||||
sg docker -c "docker compose -f 'docker-compose.dev-server.yml' down" 2>/dev/null || true
|
||||
else
|
||||
$DOCKER_CMD compose -f docker-compose.dev-server.yml down 2>/dev/null || true
|
||||
fi
|
||||
center_print "Done!" "$C_GREEN"
|
||||
;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user