mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-05 09:33:07 +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
|
# Use npm install instead of npm ci to correctly resolve platform-specific
|
||||||
# optional dependencies (e.g., @tailwindcss/oxide, lightningcss binaries)
|
# optional dependencies (e.g., @tailwindcss/oxide, lightningcss binaries)
|
||||||
# Skip scripts to avoid electron-builder install-app-deps which uses too much memory
|
# 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
|
- name: Install Linux native bindings
|
||||||
shell: bash
|
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
|
cache-dependency-path: package-lock.json
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install --ignore-scripts
|
run: npm install --ignore-scripts --force
|
||||||
|
|
||||||
- name: Check formatting
|
- name: Check formatting
|
||||||
run: npm run format:check
|
run: npm run format:check
|
||||||
|
|||||||
13
.github/workflows/release.yml
vendored
13
.github/workflows/release.yml
vendored
@@ -35,6 +35,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
check-lockfile: 'true'
|
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)
|
- name: Build Electron app (macOS)
|
||||||
if: matrix.os == 'macos-latest'
|
if: matrix.os == 'macos-latest'
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -73,7 +78,7 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: linux-builds
|
name: linux-builds
|
||||||
path: apps/ui/release/*.{AppImage,deb}
|
path: apps/ui/release/*.{AppImage,deb,rpm}
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
|
||||||
upload:
|
upload:
|
||||||
@@ -104,8 +109,8 @@ jobs:
|
|||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
artifacts/macos-builds/*
|
artifacts/macos-builds/*.{dmg,zip,blockmap}
|
||||||
artifacts/windows-builds/*
|
artifacts/windows-builds/*.{exe,blockmap}
|
||||||
artifacts/linux-builds/*
|
artifacts/linux-builds/*.{AppImage,deb,rpm,blockmap}
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
21
README.md
21
README.md
@@ -214,11 +214,30 @@ npm run build:electron
|
|||||||
# Platform-specific builds
|
# Platform-specific builds
|
||||||
npm run build:electron:mac # macOS (DMG + ZIP, x64 + arm64)
|
npm run build:electron:mac # macOS (DMG + ZIP, x64 + arm64)
|
||||||
npm run build:electron:win # Windows (NSIS installer, x64)
|
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/
|
# 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 Deployment
|
||||||
|
|
||||||
Docker provides the most secure way to run Automaker by isolating it from your host filesystem.
|
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}
|
║ ${API_KEY}
|
||||||
║ ║
|
║ ║
|
||||||
║ In Electron mode, authentication is handled automatically. ║
|
║ In Electron mode, authentication is handled automatically. ║
|
||||||
|
║ ║
|
||||||
|
║ 💡 Tip: Set AUTOMAKER_API_KEY env var to use a fixed key for dev ║
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
╚═══════════════════════════════════════════════════════════════════════╝
|
||||||
`);
|
`);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export async function loadBacklogPlan(projectPath: string): Promise<StoredBacklo
|
|||||||
const filePath = getBacklogPlanPath(projectPath);
|
const filePath = getBacklogPlanPath(projectPath);
|
||||||
const raw = await secureFs.readFile(filePath, 'utf-8');
|
const raw = await secureFs.readFile(filePath, 'utf-8');
|
||||||
const parsed = JSON.parse(raw as string) as StoredBacklogPlan;
|
const parsed = JSON.parse(raw as string) as StoredBacklogPlan;
|
||||||
if (!parsed?.result?.changes) {
|
if (!Array.isArray(parsed?.result?.changes)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return parsed;
|
return parsed;
|
||||||
|
|||||||
@@ -17,7 +17,13 @@ import { resolvePhaseModel } from '@automaker/model-resolver';
|
|||||||
import { FeatureLoader } from '../../services/feature-loader.js';
|
import { FeatureLoader } from '../../services/feature-loader.js';
|
||||||
import { ProviderFactory } from '../../providers/provider-factory.js';
|
import { ProviderFactory } from '../../providers/provider-factory.js';
|
||||||
import { extractJsonWithArray } from '../../lib/json-extractor.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 type { SettingsService } from '../../services/settings-service.js';
|
||||||
import { getAutoLoadClaudeMdSetting, getPromptCustomization } from '../../lib/settings-helpers.js';
|
import { getAutoLoadClaudeMdSetting, getPromptCustomization } from '../../lib/settings-helpers.js';
|
||||||
|
|
||||||
@@ -225,5 +231,6 @@ ${userPrompt}`;
|
|||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
setRunningState(false, null);
|
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({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
appliedChanges,
|
appliedChanges,
|
||||||
});
|
});
|
||||||
|
|
||||||
await clearBacklogPlan(projectPath);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, 'Apply backlog plan failed');
|
logError(error, 'Apply backlog plan failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export function createGenerateHandler(events: EventEmitter, settingsService?: Se
|
|||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setRunningState(false, null);
|
setRunningState(false, null);
|
||||||
|
setRunningDetails(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
|
|||||||
@@ -3,7 +3,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from 'express';
|
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() {
|
export function createStopHandler() {
|
||||||
return async (_req: Request, res: Response): Promise<void> => {
|
return async (_req: Request, res: Response): Promise<void> => {
|
||||||
@@ -12,6 +18,7 @@ export function createStopHandler() {
|
|||||||
if (abortController) {
|
if (abortController) {
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
setRunningState(false, null);
|
setRunningState(false, null);
|
||||||
|
setRunningDetails(null);
|
||||||
}
|
}
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -16,10 +16,27 @@ import { isGitRepo } from '@automaker/git-utils';
|
|||||||
import { getErrorMessage, logError, normalizePath, execEnv, isGhCliAvailable } from '../common.js';
|
import { getErrorMessage, logError, normalizePath, execEnv, isGhCliAvailable } from '../common.js';
|
||||||
import { readAllWorktreeMetadata, type WorktreePRInfo } from '../../../lib/worktree-metadata.js';
|
import { readAllWorktreeMetadata, type WorktreePRInfo } from '../../../lib/worktree-metadata.js';
|
||||||
import { createLogger } from '@automaker/utils';
|
import { createLogger } from '@automaker/utils';
|
||||||
|
import {
|
||||||
|
checkGitHubRemote,
|
||||||
|
type GitHubRemoteStatus,
|
||||||
|
} from '../../github/routes/check-github-remote.js';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
const logger = createLogger('Worktree');
|
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 {
|
interface WorktreeInfo {
|
||||||
path: string;
|
path: string;
|
||||||
branch: string;
|
branch: string;
|
||||||
@@ -121,23 +138,63 @@ async function scanWorktreesDirectory(
|
|||||||
return discovered;
|
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.
|
* 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.
|
* 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>> {
|
async function fetchGitHubPRs(projectPath: string): Promise<Map<string, WorktreePRInfo>> {
|
||||||
const prMap = new Map<string, WorktreePRInfo>();
|
const prMap = new Map<string, WorktreePRInfo>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if gh CLI is available
|
// Check GitHub remote status (uses cache to avoid repeated warnings)
|
||||||
const ghAvailable = await isGhCliAvailable();
|
const remoteStatus = await getGitHubRemoteStatus(projectPath);
|
||||||
if (!ghAvailable) {
|
|
||||||
|
// If gh CLI not available or no GitHub remote, return empty silently
|
||||||
|
if (!remoteStatus || !remoteStatus.hasGitHubRemote) {
|
||||||
return prMap;
|
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
|
// Fetch open PRs from GitHub
|
||||||
const { stdout } = await execAsync(
|
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 }
|
{ cwd: projectPath, env: execEnv, timeout: 15000 }
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -170,9 +227,10 @@ async function fetchGitHubPRs(projectPath: string): Promise<Map<string, Worktree
|
|||||||
export function createListHandler() {
|
export function createListHandler() {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { projectPath, includeDetails } = req.body as {
|
const { projectPath, includeDetails, forceRefreshGitHub } = req.body as {
|
||||||
projectPath: string;
|
projectPath: string;
|
||||||
includeDetails?: boolean;
|
includeDetails?: boolean;
|
||||||
|
forceRefreshGitHub?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!projectPath) {
|
if (!projectPath) {
|
||||||
@@ -180,6 +238,12 @@ export function createListHandler() {
|
|||||||
return;
|
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))) {
|
if (!(await isGitRepo(projectPath))) {
|
||||||
res.json({ success: true, worktrees: [] });
|
res.json({ success: true, worktrees: [] });
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -220,12 +220,34 @@
|
|||||||
"arch": [
|
"arch": [
|
||||||
"x64"
|
"x64"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": "rpm",
|
||||||
|
"arch": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"category": "Development",
|
"category": "Development",
|
||||||
"icon": "public/logo_larger.png",
|
"icon": "public/logo_larger.png",
|
||||||
"maintainer": "webdevcody@gmail.com",
|
"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": {
|
"nsis": {
|
||||||
"oneClick": false,
|
"oneClick": false,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
AlertCircle,
|
AlertCircle,
|
||||||
ListChecks,
|
ListChecks,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn, generateUUID } from '@/lib/utils';
|
||||||
|
|
||||||
const logger = createLogger('AnalysisView');
|
const logger = createLogger('AnalysisView');
|
||||||
|
|
||||||
@@ -638,7 +638,7 @@ ${Object.entries(projectAnalysis.filesByExtension)
|
|||||||
|
|
||||||
for (const detectedFeature of detectedFeatures) {
|
for (const detectedFeature of detectedFeatures) {
|
||||||
await api.features.create(currentProject.path, {
|
await api.features.create(currentProject.path, {
|
||||||
id: crypto.randomUUID(),
|
id: generateUUID(),
|
||||||
category: detectedFeature.category,
|
category: detectedFeature.category,
|
||||||
description: detectedFeature.description,
|
description: detectedFeature.description,
|
||||||
status: 'backlog',
|
status: 'backlog',
|
||||||
|
|||||||
@@ -39,7 +39,10 @@ export function useWorktrees({
|
|||||||
logger.warn('Worktree API not available');
|
logger.warn('Worktree API not available');
|
||||||
return;
|
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) {
|
if (result.success && result.worktrees) {
|
||||||
setWorktrees(result.worktrees);
|
setWorktrees(result.worktrees);
|
||||||
setWorktreesInStore(projectPath, result.worktrees);
|
setWorktreesInStore(projectPath, result.worktrees);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||||
import { LoadingState } from '@/components/ui/loading-state';
|
import { LoadingState } from '@/components/ui/loading-state';
|
||||||
import { ErrorState } from '@/components/ui/error-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 { toast } from 'sonner';
|
||||||
import { useGithubIssues, useIssueValidation, useIssuesFilter } from './github-issues-view/hooks';
|
import { useGithubIssues, useIssueValidation, useIssuesFilter } from './github-issues-view/hooks';
|
||||||
import { IssueRow, IssueDetailPanel, IssuesListHeader } from './github-issues-view/components';
|
import { IssueRow, IssueDetailPanel, IssuesListHeader } from './github-issues-view/components';
|
||||||
@@ -137,7 +137,7 @@ export function GitHubIssuesView() {
|
|||||||
.join('\n');
|
.join('\n');
|
||||||
|
|
||||||
const feature = {
|
const feature = {
|
||||||
id: `issue-${issue.number}-${crypto.randomUUID()}`,
|
id: `issue-${issue.number}-${generateUUID()}`,
|
||||||
title: issue.title,
|
title: issue.title,
|
||||||
description,
|
description,
|
||||||
category: 'From GitHub',
|
category: 'From GitHub',
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Card, CardContent } from '@/components/ui/card';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Bot, Send, User, Loader2, Sparkles, FileText, ArrowLeft, CheckCircle } from 'lucide-react';
|
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 { getElectronAPI } from '@/lib/electron';
|
||||||
import { Markdown } from '@/components/ui/markdown';
|
import { Markdown } from '@/components/ui/markdown';
|
||||||
import { useFileBrowser } from '@/contexts/file-browser-context';
|
import { useFileBrowser } from '@/contexts/file-browser-context';
|
||||||
@@ -345,7 +345,7 @@ export function InterviewView() {
|
|||||||
|
|
||||||
// Create initial feature in the features folder
|
// Create initial feature in the features folder
|
||||||
const initialFeature: Feature = {
|
const initialFeature: Feature = {
|
||||||
id: crypto.randomUUID(),
|
id: generateUUID(),
|
||||||
category: 'Core',
|
category: 'Core',
|
||||||
description: 'Initial project setup',
|
description: 'Initial project setup',
|
||||||
status: 'backlog' as const,
|
status: 'backlog' as const,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import type {
|
|||||||
EventHookHttpAction,
|
EventHookHttpAction,
|
||||||
} from '@automaker/types';
|
} from '@automaker/types';
|
||||||
import { EVENT_HOOK_TRIGGER_LABELS } from '@automaker/types';
|
import { EVENT_HOOK_TRIGGER_LABELS } from '@automaker/types';
|
||||||
|
import { generateUUID } from '@/lib/utils';
|
||||||
|
|
||||||
interface EventHookDialogProps {
|
interface EventHookDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -109,7 +110,7 @@ export function EventHookDialog({ open, onOpenChange, editingHook, onSave }: Eve
|
|||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
const hook: EventHook = {
|
const hook: EventHook = {
|
||||||
id: editingHook?.id || crypto.randomUUID(),
|
id: editingHook?.id || generateUUID(),
|
||||||
name: name.trim() || undefined,
|
name: name.trim() || undefined,
|
||||||
trigger,
|
trigger,
|
||||||
enabled: editingHook?.enabled ?? true,
|
enabled: editingHook?.enabled ?? true,
|
||||||
|
|||||||
@@ -166,6 +166,7 @@ export function PhaseModelSelector({
|
|||||||
codexModelsLoading,
|
codexModelsLoading,
|
||||||
fetchCodexModels,
|
fetchCodexModels,
|
||||||
dynamicOpencodeModels,
|
dynamicOpencodeModels,
|
||||||
|
enabledDynamicModelIds,
|
||||||
opencodeModelsLoading,
|
opencodeModelsLoading,
|
||||||
fetchOpencodeModels,
|
fetchOpencodeModels,
|
||||||
disabledProviders,
|
disabledProviders,
|
||||||
@@ -383,7 +384,10 @@ export function PhaseModelSelector({
|
|||||||
const staticModels = [...OPENCODE_MODELS];
|
const staticModels = [...OPENCODE_MODELS];
|
||||||
|
|
||||||
// Add dynamic models (convert ModelDefinition to ModelOption)
|
// Add dynamic models (convert ModelDefinition to ModelOption)
|
||||||
const dynamicModelOptions: ModelOption[] = dynamicOpencodeModels.map((model) => ({
|
// 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,
|
id: model.id,
|
||||||
label: model.name,
|
label: model.name,
|
||||||
description: model.description,
|
description: model.description,
|
||||||
@@ -397,7 +401,7 @@ export function PhaseModelSelector({
|
|||||||
const uniqueDynamic = dynamicModelOptions.filter((m) => !staticIds.has(m.id));
|
const uniqueDynamic = dynamicModelOptions.filter((m) => !staticIds.has(m.id));
|
||||||
|
|
||||||
return [...staticModels, ...uniqueDynamic];
|
return [...staticModels, ...uniqueDynamic];
|
||||||
}, [dynamicOpencodeModels]);
|
}, [dynamicOpencodeModels, enabledDynamicModelIds]);
|
||||||
|
|
||||||
// Group models (filtering out disabled providers)
|
// Group models (filtering out disabled providers)
|
||||||
const { favorites, claude, cursor, codex, opencode } = useMemo(() => {
|
const { favorites, claude, cursor, codex, opencode } = useMemo(() => {
|
||||||
|
|||||||
@@ -611,16 +611,16 @@ export function OpencodeModelConfiguration({
|
|||||||
Dynamic
|
Dynamic
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
{models.length > 0 && (
|
{filteredModels.length > 0 && (
|
||||||
<div className={OPENCODE_SELECT_ALL_CONTAINER_CLASS}>
|
<div className={OPENCODE_SELECT_ALL_CONTAINER_CLASS}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={getSelectionState(
|
checked={getSelectionState(
|
||||||
models.map((model) => model.id),
|
filteredModels.map((model) => model.id),
|
||||||
enabledDynamicModelIds
|
enabledDynamicModelIds
|
||||||
)}
|
)}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) =>
|
||||||
toggleProviderDynamicModels(
|
toggleProviderDynamicModels(
|
||||||
models.map((model) => model.id),
|
filteredModels.map((model) => model.id),
|
||||||
checked
|
checked
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1596,10 +1596,15 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
|||||||
return { success: true, worktrees: [] };
|
return { success: true, worktrees: [] };
|
||||||
},
|
},
|
||||||
|
|
||||||
listAll: async (projectPath: string, includeDetails?: boolean) => {
|
listAll: async (
|
||||||
|
projectPath: string,
|
||||||
|
includeDetails?: boolean,
|
||||||
|
forceRefreshGitHub?: boolean
|
||||||
|
) => {
|
||||||
console.log('[Mock] Listing all worktrees:', {
|
console.log('[Mock] Listing all worktrees:', {
|
||||||
projectPath,
|
projectPath,
|
||||||
includeDetails,
|
includeDetails,
|
||||||
|
forceRefreshGitHub,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1724,8 +1724,8 @@ export class HttpApiClient implements ElectronAPI {
|
|||||||
getStatus: (projectPath: string, featureId: string) =>
|
getStatus: (projectPath: string, featureId: string) =>
|
||||||
this.post('/api/worktree/status', { projectPath, featureId }),
|
this.post('/api/worktree/status', { projectPath, featureId }),
|
||||||
list: (projectPath: string) => this.post('/api/worktree/list', { projectPath }),
|
list: (projectPath: string) => this.post('/api/worktree/list', { projectPath }),
|
||||||
listAll: (projectPath: string, includeDetails?: boolean) =>
|
listAll: (projectPath: string, includeDetails?: boolean, forceRefreshGitHub?: boolean) =>
|
||||||
this.post('/api/worktree/list', { projectPath, includeDetails }),
|
this.post('/api/worktree/list', { projectPath, includeDetails, forceRefreshGitHub }),
|
||||||
create: (projectPath: string, branchName: string, baseBranch?: string) =>
|
create: (projectPath: string, branchName: string, baseBranch?: string) =>
|
||||||
this.post('/api/worktree/create', {
|
this.post('/api/worktree/create', {
|
||||||
projectPath,
|
projectPath,
|
||||||
|
|||||||
@@ -124,3 +124,39 @@ export const isMac =
|
|||||||
: typeof navigator !== 'undefined' &&
|
: typeof navigator !== 'undefined' &&
|
||||||
(/Mac/.test(navigator.userAgent) ||
|
(/Mac/.test(navigator.userAgent) ||
|
||||||
(navigator.platform ? navigator.platform.toLowerCase().includes('mac') : false));
|
(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)
|
// List all worktrees with details (for worktree selector)
|
||||||
listAll: (
|
listAll: (
|
||||||
projectPath: string,
|
projectPath: string,
|
||||||
includeDetails?: boolean
|
includeDetails?: boolean,
|
||||||
|
forceRefreshGitHub?: boolean
|
||||||
) => Promise<{
|
) => Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
worktrees?: Array<{
|
worktrees?: Array<{
|
||||||
|
|||||||
@@ -130,8 +130,8 @@ test.describe('Feature Manual Review Flow', () => {
|
|||||||
await page.waitForTimeout(300);
|
await page.waitForTimeout(300);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify we're on the correct project (project name appears in sidebar button)
|
// Verify we're on the correct project (project switcher button shows project name)
|
||||||
await expect(page.getByRole('button', { name: new RegExp(projectName) })).toBeVisible({
|
await expect(page.getByTestId(`project-switcher-project-${projectName}`)).toBeVisible({
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ test.describe('Project Creation', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for project to be set as current and visible on the page
|
// Wait for 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
|
||||||
await expect(page.getByRole('button', { name: new RegExp(projectName) })).toBeVisible({
|
await expect(page.getByTestId(`project-switcher-project-${projectName}`)).toBeVisible({
|
||||||
timeout: 15000,
|
timeout: 15000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -156,9 +156,9 @@ test.describe('Open Project', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for a project to be set as current and visible on the page
|
// 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) {
|
if (targetProjectName) {
|
||||||
await expect(page.getByRole('button', { name: new RegExp(targetProjectName) })).toBeVisible({
|
await expect(page.getByTestId(`project-switcher-project-${targetProjectName}`)).toBeVisible({
|
||||||
timeout: 15000,
|
timeout: 15000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,19 +74,17 @@ services:
|
|||||||
command:
|
command:
|
||||||
- -c
|
- -c
|
||||||
- |
|
- |
|
||||||
# Fix permissions on node_modules (created as root by Docker volume)
|
# Install as root to avoid permission issues with named volumes
|
||||||
echo 'Fixing node_modules permissions...'
|
# Use --force to skip platform-specific devDependencies (dmg-license is macOS-only)
|
||||||
chown -R automaker:automaker /app/node_modules 2>/dev/null || true
|
|
||||||
|
|
||||||
# Run the rest as automaker user
|
|
||||||
exec gosu automaker sh -c "
|
|
||||||
echo 'Installing dependencies...' &&
|
echo 'Installing dependencies...' &&
|
||||||
npm install &&
|
npm ci --legacy-peer-deps --force &&
|
||||||
echo 'Building shared packages...' &&
|
echo 'Building shared packages...' &&
|
||||||
npm run build:packages &&
|
npm run build:packages &&
|
||||||
|
|
||||||
|
# Fix permissions and start server as automaker user
|
||||||
|
chown -R automaker:automaker /app/node_modules &&
|
||||||
echo 'Starting server in development mode...' &&
|
echo 'Starting server in development mode...' &&
|
||||||
npm run _dev:server
|
exec gosu automaker npm run _dev:server
|
||||||
"
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ['CMD', 'curl', '-f', 'http://localhost:3008/api/health']
|
test: ['CMD', 'curl', '-f', 'http://localhost:3008/api/health']
|
||||||
interval: 10s
|
interval: 10s
|
||||||
|
|||||||
@@ -75,19 +75,17 @@ services:
|
|||||||
command:
|
command:
|
||||||
- -c
|
- -c
|
||||||
- |
|
- |
|
||||||
# Fix permissions on node_modules (created as root by Docker volume)
|
# Install as root to avoid permission issues with named volumes
|
||||||
echo 'Fixing node_modules permissions...'
|
# Use --force to skip platform-specific devDependencies (dmg-license is macOS-only)
|
||||||
chown -R automaker:automaker /app/node_modules 2>/dev/null || true
|
|
||||||
|
|
||||||
# Run the rest as automaker user
|
|
||||||
exec gosu automaker sh -c "
|
|
||||||
echo 'Installing dependencies...' &&
|
echo 'Installing dependencies...' &&
|
||||||
npm install &&
|
npm ci --legacy-peer-deps --force &&
|
||||||
echo 'Building shared packages...' &&
|
echo 'Building shared packages...' &&
|
||||||
npm run build:packages &&
|
npm run build:packages &&
|
||||||
|
|
||||||
|
# Fix permissions and start server as automaker user
|
||||||
|
chown -R automaker:automaker /app/node_modules &&
|
||||||
echo 'Starting server in development mode...' &&
|
echo 'Starting server in development mode...' &&
|
||||||
npm run _dev:server
|
exec gosu automaker npm run _dev:server
|
||||||
"
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ['CMD', 'curl', '-f', 'http://localhost:3008/api/health']
|
test: ['CMD', 'curl', '-f', 'http://localhost:3008/api/health']
|
||||||
interval: 10s
|
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
|
// - github-copilot/gpt-4o
|
||||||
// - google/gemini-2.5-pro
|
// - google/gemini-2.5-pro
|
||||||
// - xai/grok-3
|
// - 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('://')) {
|
if (model.includes('/') && !model.includes('://')) {
|
||||||
const parts = model.split('/');
|
const slashIndex = model.indexOf('/');
|
||||||
// Valid dynamic model format: provider/model-name (exactly 2 parts)
|
const providerId = model.substring(0, slashIndex);
|
||||||
if (parts.length === 2 && parts[0].length > 0 && parts[1].length > 0) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
package-lock.json
generated
31
package-lock.json
generated
@@ -18,6 +18,7 @@
|
|||||||
"tree-kill": "1.2.2"
|
"tree-kill": "1.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"dmg-license": "^1.0.11",
|
||||||
"husky": "9.1.7",
|
"husky": "9.1.7",
|
||||||
"lint-staged": "16.2.7",
|
"lint-staged": "16.2.7",
|
||||||
"prettier": "3.7.4",
|
"prettier": "3.7.4",
|
||||||
@@ -6132,7 +6133,6 @@
|
|||||||
"integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==",
|
"integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"xmlbuilder": ">=11.0.1"
|
"xmlbuilder": ">=11.0.1"
|
||||||
@@ -6214,8 +6214,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz",
|
||||||
"integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==",
|
"integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/ws": {
|
"node_modules/@types/ws": {
|
||||||
"version": "8.18.1",
|
"version": "8.18.1",
|
||||||
@@ -7240,7 +7239,6 @@
|
|||||||
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
|
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.8"
|
"node": ">=0.8"
|
||||||
}
|
}
|
||||||
@@ -7293,7 +7291,6 @@
|
|||||||
"integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
|
"integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
@@ -8038,7 +8035,6 @@
|
|||||||
"integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==",
|
"integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"slice-ansi": "^3.0.0",
|
"slice-ansi": "^3.0.0",
|
||||||
"string-width": "^4.2.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",
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
|
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/cors": {
|
"node_modules/cors": {
|
||||||
"version": "2.8.5",
|
"version": "2.8.5",
|
||||||
@@ -8336,7 +8331,6 @@
|
|||||||
"integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==",
|
"integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer": "^5.1.0"
|
"buffer": "^5.1.0"
|
||||||
}
|
}
|
||||||
@@ -8800,7 +8794,6 @@
|
|||||||
"integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==",
|
"integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
@@ -9693,8 +9686,7 @@
|
|||||||
"engines": [
|
"engines": [
|
||||||
"node >=0.6.0"
|
"node >=0.6.0"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
@@ -10658,7 +10650,6 @@
|
|||||||
"integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==",
|
"integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
@@ -11283,6 +11274,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -11304,6 +11296,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -11346,6 +11339,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -11367,6 +11361,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -11388,6 +11383,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -11409,6 +11405,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -11430,6 +11427,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -11451,6 +11449,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -11472,6 +11471,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -13078,8 +13078,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz",
|
||||||
"integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==",
|
"integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/node-api-version": {
|
"node_modules/node-api-version": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
@@ -14596,7 +14595,6 @@
|
|||||||
"integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==",
|
"integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^4.0.0",
|
"ansi-styles": "^4.0.0",
|
||||||
"astral-regex": "^2.0.0",
|
"astral-regex": "^2.0.0",
|
||||||
@@ -15713,7 +15711,6 @@
|
|||||||
"integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
|
"integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"assert-plus": "^1.0.0",
|
"assert-plus": "^1.0.0",
|
||||||
"core-util-is": "1.0.2",
|
"core-util-is": "1.0.2",
|
||||||
|
|||||||
@@ -67,6 +67,7 @@
|
|||||||
"tree-kill": "1.2.2"
|
"tree-kill": "1.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"dmg-license": "^1.0.11",
|
||||||
"husky": "9.1.7",
|
"husky": "9.1.7",
|
||||||
"lint-staged": "16.2.7",
|
"lint-staged": "16.2.7",
|
||||||
"prettier": "3.7.4",
|
"prettier": "3.7.4",
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ DEFAULT_SERVER_PORT=3008
|
|||||||
WEB_PORT=$DEFAULT_WEB_PORT
|
WEB_PORT=$DEFAULT_WEB_PORT
|
||||||
SERVER_PORT=$DEFAULT_SERVER_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
|
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
|
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
|
fi
|
||||||
|
|
||||||
# ANSI Color codes (256-color palette)
|
# ANSI Color codes (256-color palette)
|
||||||
@@ -200,6 +200,8 @@ check_required_commands() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DOCKER_CMD="docker"
|
||||||
|
|
||||||
check_docker() {
|
check_docker() {
|
||||||
if ! command -v docker &> /dev/null; then
|
if ! command -v docker &> /dev/null; then
|
||||||
echo "${C_RED}Error:${RESET} Docker is not installed or not in PATH"
|
echo "${C_RED}Error:${RESET} Docker is not installed or not in PATH"
|
||||||
@@ -207,12 +209,22 @@ check_docker() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! docker info &> /dev/null; then
|
if ! docker info &> /dev/null 2>&1; then
|
||||||
echo "${C_RED}Error:${RESET} Docker daemon is not running"
|
if sg docker -c "docker info" &> /dev/null 2>&1; then
|
||||||
echo "Please start Docker and try again"
|
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
|
return 1
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
export DOCKER_CMD
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +303,11 @@ check_running_containers() {
|
|||||||
local running_containers=""
|
local running_containers=""
|
||||||
|
|
||||||
# Get list of running automaker 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
|
if [ -n "$running_containers" ] && [ "$running_containers" != " " ]; then
|
||||||
get_term_size
|
get_term_size
|
||||||
@@ -319,9 +335,13 @@ check_running_containers() {
|
|||||||
[sS]|[sS][tT][oO][pP])
|
[sS]|[sS][tT][oO][pP])
|
||||||
echo ""
|
echo ""
|
||||||
center_print "Stopping existing containers..." "$C_YELLOW"
|
center_print "Stopping existing containers..." "$C_YELLOW"
|
||||||
docker compose -f "$compose_file" down 2>/dev/null || true
|
if [ "$DOCKER_CMD" = "sg docker -c" ]; then
|
||||||
# Also try stopping any orphaned containers
|
sg docker -c "docker compose -f '$compose_file' down" 2>/dev/null || true
|
||||||
docker ps --filter "name=automaker-dev" -q 2>/dev/null | xargs -r docker stop 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"
|
center_print "✓ Containers stopped" "$C_GREEN"
|
||||||
echo ""
|
echo ""
|
||||||
return 0 # Continue with fresh start
|
return 0 # Continue with fresh start
|
||||||
@@ -329,7 +349,11 @@ check_running_containers() {
|
|||||||
[rR]|[rR][eE][sS][tT][aA][rR][tT])
|
[rR]|[rR][eE][sS][tT][aA][rR][tT])
|
||||||
echo ""
|
echo ""
|
||||||
center_print "Stopping and rebuilding containers..." "$C_YELLOW"
|
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"
|
center_print "✓ Ready to rebuild" "$C_GREEN"
|
||||||
echo ""
|
echo ""
|
||||||
return 0 # Continue with rebuild
|
return 0 # Continue with rebuild
|
||||||
@@ -1170,10 +1194,18 @@ case $MODE in
|
|||||||
center_print "API: http://localhost:$DEFAULT_SERVER_PORT" "$C_GREEN"
|
center_print "API: http://localhost:$DEFAULT_SERVER_PORT" "$C_GREEN"
|
||||||
center_print "Press Ctrl+C to detach" "$C_MUTE"
|
center_print "Press Ctrl+C to detach" "$C_MUTE"
|
||||||
echo ""
|
echo ""
|
||||||
|
if [ "$DOCKER_CMD" = "sg docker -c" ]; then
|
||||||
if [ -f "docker-compose.override.yml" ]; then
|
if [ -f "docker-compose.override.yml" ]; then
|
||||||
docker compose -f docker-compose.dev.yml -f docker-compose.override.yml logs -f
|
sg docker -c "docker compose -f 'docker-compose.dev.yml' -f 'docker-compose.override.yml' logs -f"
|
||||||
else
|
else
|
||||||
docker compose -f docker-compose.dev.yml logs -f
|
sg docker -c "docker compose -f 'docker-compose.dev.yml' logs -f"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
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
|
fi
|
||||||
else
|
else
|
||||||
echo ""
|
echo ""
|
||||||
@@ -1192,10 +1224,18 @@ case $MODE in
|
|||||||
echo ""
|
echo ""
|
||||||
center_print "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" "$C_GRAY"
|
center_print "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" "$C_GRAY"
|
||||||
echo ""
|
echo ""
|
||||||
|
if [ "$DOCKER_CMD" = "sg docker -c" ]; then
|
||||||
if [ -f "docker-compose.override.yml" ]; then
|
if [ -f "docker-compose.override.yml" ]; then
|
||||||
docker compose -f docker-compose.dev.yml -f docker-compose.override.yml up --build
|
sg docker -c "docker compose -f 'docker-compose.dev.yml' -f 'docker-compose.override.yml' up --build"
|
||||||
else
|
else
|
||||||
docker compose -f docker-compose.dev.yml up --build
|
sg docker -c "docker compose -f 'docker-compose.dev.yml' up --build"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
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
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
@@ -1235,10 +1275,18 @@ case $MODE in
|
|||||||
else
|
else
|
||||||
center_print "Starting Docker server container..." "$C_MUTE"
|
center_print "Starting Docker server container..." "$C_MUTE"
|
||||||
echo ""
|
echo ""
|
||||||
|
if [ "$DOCKER_CMD" = "sg docker -c" ]; then
|
||||||
if [ -f "docker-compose.override.yml" ]; then
|
if [ -f "docker-compose.override.yml" ]; then
|
||||||
docker compose -f docker-compose.dev-server.yml -f docker-compose.override.yml up --build &
|
sg docker -c "docker compose -f 'docker-compose.dev-server.yml' -f 'docker-compose.override.yml' up --build" &
|
||||||
else
|
else
|
||||||
docker compose -f docker-compose.dev-server.yml up --build &
|
sg docker -c "docker compose -f 'docker-compose.dev-server.yml' up --build" &
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
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
|
fi
|
||||||
DOCKER_PID=$!
|
DOCKER_PID=$!
|
||||||
fi
|
fi
|
||||||
@@ -1284,7 +1332,11 @@ case $MODE in
|
|||||||
echo ""
|
echo ""
|
||||||
center_print "Shutting down Docker container..." "$C_MUTE"
|
center_print "Shutting down Docker container..." "$C_MUTE"
|
||||||
[ -n "$DOCKER_PID" ] && kill $DOCKER_PID 2>/dev/null || true
|
[ -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"
|
center_print "Done!" "$C_GREEN"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
Reference in New Issue
Block a user