Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0455995b1a |
@@ -105,7 +105,7 @@ jobs:
|
||||
apple-team-id: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: macos-intel-builds
|
||||
path: |
|
||||
@@ -157,7 +157,7 @@ jobs:
|
||||
apple-team-id: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: macos-arm64-builds
|
||||
path: |
|
||||
@@ -315,7 +315,7 @@ jobs:
|
||||
run: echo "::warning::Windows signing skipped - AZURE_CLIENT_ID not configured. The .exe will be unsigned."
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: windows-builds
|
||||
path: |
|
||||
@@ -364,7 +364,7 @@ jobs:
|
||||
run: cd apps/desktop && npm run verify:linux
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: linux-builds
|
||||
path: |
|
||||
@@ -404,13 +404,13 @@ jobs:
|
||||
arm64-dmg-file: ${{ needs.build-macos-arm64.outputs.dmg_file }}
|
||||
|
||||
- name: Upload stapled Intel DMG
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: macos-intel-stapled
|
||||
path: intel/*.dmg
|
||||
|
||||
- name: Upload stapled ARM64 DMG
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: macos-arm64-stapled
|
||||
path: arm64/*.dmg
|
||||
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
Get-ChildItem $archiveName
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: node-pty-win32-${{ matrix.arch }}
|
||||
path: apps/desktop/node-pty-*.zip
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
find artifacts -type f -name "*.zip"
|
||||
|
||||
- name: Upload combined artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: node-pty-prebuilds-all
|
||||
path: artifacts/**/*.zip
|
||||
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
|
||||
- name: Upload coverage report
|
||||
if: matrix.os == 'ubuntu-latest' && always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: coverage-report
|
||||
path: apps/desktop/coverage/
|
||||
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
- name: Upload E2E report
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: e2e-report
|
||||
path: |
|
||||
|
||||
@@ -201,7 +201,7 @@ jobs:
|
||||
# Upload changelog as artifact for release.yml to use
|
||||
- name: Upload changelog artifact
|
||||
if: steps.check.outputs.should_release == 'true' && steps.changelog.outputs.changelog_valid == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: changelog-${{ steps.check.outputs.new_version }}
|
||||
path: changelog-extract.md
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
apple-team-id: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: macos-intel-builds
|
||||
path: |
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
apple-team-id: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: macos-arm64-builds
|
||||
path: |
|
||||
@@ -257,7 +257,7 @@ jobs:
|
||||
run: echo "::warning::Windows signing skipped - AZURE_CLIENT_ID not configured. The .exe will be unsigned."
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: windows-builds
|
||||
path: |
|
||||
@@ -300,7 +300,7 @@ jobs:
|
||||
run: cd apps/desktop && npm run verify:linux
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: linux-builds
|
||||
path: |
|
||||
@@ -341,13 +341,13 @@ jobs:
|
||||
arm64-dmg-file: ${{ needs.build-macos-arm64.outputs.dmg_file }}
|
||||
|
||||
- name: Upload stapled Intel DMG
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: macos-intel-stapled
|
||||
path: intel/*.dmg
|
||||
|
||||
- name: Upload stapled ARM64 DMG
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: macos-arm64-stapled
|
||||
path: arm64/*.dmg
|
||||
|
||||
@@ -36,18 +36,18 @@
|
||||
> ⚠️ Beta releases may contain bugs and breaking changes. [View all releases](https://github.com/AndyMik90/Auto-Claude/releases)
|
||||
|
||||
<!-- BETA_VERSION_BADGE -->
|
||||
[](https://github.com/AndyMik90/Auto-Claude/releases/tag/v2.8.0-beta.5)
|
||||
[](https://github.com/AndyMik90/Auto-Claude/releases/tag/v2.8.0-beta.4)
|
||||
<!-- BETA_VERSION_BADGE_END -->
|
||||
|
||||
<!-- BETA_DOWNLOADS -->
|
||||
| Platform | Download |
|
||||
|----------|----------|
|
||||
| **Windows** | [Aperant-2.8.0-beta.5-win32-x64.exe](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Aperant-2.8.0-beta.5-win32-x64.exe) |
|
||||
| **macOS (Apple Silicon)** | [Aperant-2.8.0-beta.5-darwin-arm64.dmg](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Aperant-2.8.0-beta.5-darwin-arm64.dmg) |
|
||||
| **macOS (Intel)** | [Aperant-2.8.0-beta.5-darwin-x64.dmg](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Aperant-2.8.0-beta.5-darwin-x64.dmg) |
|
||||
| **Linux** | [Aperant-2.8.0-beta.5-linux-x86_64.AppImage](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Aperant-2.8.0-beta.5-linux-x86_64.AppImage) |
|
||||
| **Linux (Debian)** | [Aperant-2.8.0-beta.5-linux-amd64.deb](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Aperant-2.8.0-beta.5-linux-amd64.deb) |
|
||||
| **Linux (Flatpak)** | [Aperant-2.8.0-beta.5-linux-x86_64.flatpak](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Aperant-2.8.0-beta.5-linux-x86_64.flatpak) |
|
||||
| **Windows** | [Auto-Claude-2.8.0-beta.4-win32-x64.exe](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.4/Auto-Claude-2.8.0-beta.4-win32-x64.exe) |
|
||||
| **macOS (Apple Silicon)** | [Auto-Claude-2.8.0-beta.4-darwin-arm64.dmg](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.4/Auto-Claude-2.8.0-beta.4-darwin-arm64.dmg) |
|
||||
| **macOS (Intel)** | [Auto-Claude-2.8.0-beta.4-darwin-x64.dmg](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.4/Auto-Claude-2.8.0-beta.4-darwin-x64.dmg) |
|
||||
| **Linux** | [Auto-Claude-2.8.0-beta.4-linux-x86_64.AppImage](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.4/Auto-Claude-2.8.0-beta.4-linux-x86_64.AppImage) |
|
||||
| **Linux (Debian)** | [Auto-Claude-2.8.0-beta.4-linux-amd64.deb](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.4/Auto-Claude-2.8.0-beta.4-linux-amd64.deb) |
|
||||
| **Linux (Flatpak)** | [Auto-Claude-2.8.0-beta.4-linux-x86_64.flatpak](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.4/Auto-Claude-2.8.0-beta.4-linux-x86_64.flatpak) |
|
||||
<!-- BETA_DOWNLOADS_END -->
|
||||
|
||||
> All releases include SHA256 checksums and VirusTotal scan results for security verification.
|
||||
|
||||
@@ -78,9 +78,7 @@ export default defineConfig({
|
||||
// spawned via `new Worker(path)` from WorkerBridge
|
||||
'ai/agent/worker': resolve(__dirname, 'src/main/ai/agent/worker.ts'),
|
||||
},
|
||||
// Native modules that must remain external (loaded from disk, not bundled).
|
||||
// @libsql/client is loaded lazily via globalThis.require() and resolved
|
||||
// from extraResources/node_modules via Module.globalPaths (see index.ts).
|
||||
// Only node-pty needs to be external (native module rebuilt by electron-builder)
|
||||
external: ['@lydell/node-pty']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "aperant",
|
||||
"name": "auto-claude-ui",
|
||||
"version": "2.8.0-beta.1",
|
||||
"type": "module",
|
||||
"description": "Autonomous multi-agent coding framework",
|
||||
"homepage": "https://github.com/AndyMik90/Aperant",
|
||||
"description": "Desktop UI for Auto Claude autonomous coding framework",
|
||||
"homepage": "https://github.com/AndyMik90/Auto-Claude",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/AndyMik90/Aperant.git"
|
||||
"url": "https://github.com/AndyMik90/Auto-Claude.git"
|
||||
},
|
||||
"main": "./out/main/index.js",
|
||||
"author": {
|
||||
"name": "Aperant Team",
|
||||
"name": "Auto Claude Team",
|
||||
"email": "119136210+AndyMik90@users.noreply.github.com"
|
||||
},
|
||||
"license": "AGPL-3.0",
|
||||
@@ -35,9 +35,9 @@
|
||||
"package:flatpak": "electron-builder --linux flatpak",
|
||||
"verify:linux": "node scripts/verify-linux-packages.cjs dist",
|
||||
"test:verify-linux": "node --test scripts/verify-linux-packages.test.mjs",
|
||||
"start:packaged:mac": "open dist/mac-arm64/Aperant.app || open dist/mac/Aperant.app",
|
||||
"start:packaged:win": "start \"\" \"dist\\win-unpacked\\Aperant.exe\"",
|
||||
"start:packaged:linux": "./dist/linux-unpacked/aperant",
|
||||
"start:packaged:mac": "open dist/mac-arm64/Auto-Claude.app || open dist/mac/Auto-Claude.app",
|
||||
"start:packaged:win": "start \"\" \"dist\\win-unpacked\\Auto-Claude.exe\"",
|
||||
"start:packaged:linux": "./dist/linux-unpacked/auto-claude",
|
||||
"test": "vitest run",
|
||||
"test:unit": "vitest run --exclude src/__tests__/integration/ --exclude src/__tests__/e2e/",
|
||||
"test:integration": "vitest run src/__tests__/integration/",
|
||||
@@ -50,27 +50,27 @@
|
||||
"typecheck": "tsc --noEmit --incremental"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/amazon-bedrock": "^4.0.77",
|
||||
"@ai-sdk/anthropic": "^3.0.58",
|
||||
"@ai-sdk/azure": "^3.0.42",
|
||||
"@ai-sdk/google": "^3.0.43",
|
||||
"@ai-sdk/groq": "^3.0.29",
|
||||
"@ai-sdk/mcp": "^1.0.25",
|
||||
"@ai-sdk/mistral": "^3.0.24",
|
||||
"@ai-sdk/openai": "^3.0.41",
|
||||
"@ai-sdk/openai-compatible": "^2.0.35",
|
||||
"@ai-sdk/xai": "^3.0.67",
|
||||
"@anthropic-ai/sdk": "^0.78.0",
|
||||
"@ai-sdk/amazon-bedrock": "^4.0.61",
|
||||
"@ai-sdk/anthropic": "^3.0.45",
|
||||
"@ai-sdk/azure": "^3.0.31",
|
||||
"@ai-sdk/google": "^3.0.29",
|
||||
"@ai-sdk/groq": "^3.0.24",
|
||||
"@ai-sdk/mcp": "^1.0.21",
|
||||
"@ai-sdk/mistral": "^2.0.28",
|
||||
"@ai-sdk/openai": "^3.0.30",
|
||||
"@ai-sdk/openai-compatible": "^2.0.30",
|
||||
"@ai-sdk/xai": "^3.0.57",
|
||||
"@anthropic-ai/sdk": "^0.71.2",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@libsql/client": "^0.17.0",
|
||||
"@lydell/node-pty": "^1.1.0",
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"@openrouter/ai-sdk-provider": "^2.3.1",
|
||||
"@modelcontextprotocol/sdk": "^1.26.0",
|
||||
"@openrouter/ai-sdk-provider": "^2.2.3",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
"@radix-ui/react-checkbox": "^1.1.4",
|
||||
"@radix-ui/react-collapsible": "^1.1.3",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-popover": "^1.1.15",
|
||||
@@ -84,89 +84,89 @@
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-toast": "^1.2.15",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@sentry/electron": "^7.10.0",
|
||||
"@sentry/electron": "^7.5.0",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tanstack/react-virtual": "^3.13.22",
|
||||
"@tanstack/react-virtual": "^3.13.13",
|
||||
"@tavily/core": "^0.7.2",
|
||||
"@xterm/addon-fit": "^0.11.0",
|
||||
"@xterm/addon-serialize": "^0.14.0",
|
||||
"@xterm/addon-web-links": "^0.12.0",
|
||||
"@xterm/addon-webgl": "^0.19.0",
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
"ai": "^6.0.116",
|
||||
"ai": "^6.0.91",
|
||||
"chokidar": "^5.0.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dotenv": "^17.3.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"electron-log": "^5.4.3",
|
||||
"electron-updater": "^6.8.3",
|
||||
"i18next": "^25.8.18",
|
||||
"lucide-react": "^0.577.0",
|
||||
"minimatch": "^10.2.4",
|
||||
"motion": "^12.36.0",
|
||||
"electron-updater": "^6.6.2",
|
||||
"i18next": "^25.7.3",
|
||||
"lucide-react": "^0.562.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"motion": "^12.23.26",
|
||||
"proper-lockfile": "^4.1.2",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-i18next": "^16.5.8",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-i18next": "^16.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"semver": "^7.7.4",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"semver": "^7.7.3",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"uuid": "^13.0.0",
|
||||
"web-tree-sitter": "^0.26.7",
|
||||
"xstate": "^5.28.0",
|
||||
"zod": "^4.3.6",
|
||||
"zustand": "^5.0.11"
|
||||
"web-tree-sitter": "^0.26.5",
|
||||
"xstate": "^5.26.0",
|
||||
"zod": "^4.2.1",
|
||||
"zustand": "^5.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.4.7",
|
||||
"@biomejs/biome": "2.3.11",
|
||||
"@electron-toolkit/preload": "^3.0.2",
|
||||
"@electron-toolkit/utils": "^4.0.0",
|
||||
"@electron/rebuild": "^4.0.3",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@tailwindcss/postcss": "^4.2.1",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@electron/rebuild": "^4.0.2",
|
||||
"@playwright/test": "^1.52.0",
|
||||
"@tailwindcss/postcss": "^4.1.17",
|
||||
"@testing-library/dom": "^10.0.0",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/react": "^16.1.0",
|
||||
"@types/minimatch": "^6.0.0",
|
||||
"@types/node": "^25.5.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/node": "^25.0.0",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@types/uuid": "^11.0.0",
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
"@vitest/coverage-v8": "^4.1.0",
|
||||
"autoprefixer": "^10.4.27",
|
||||
"autoprefixer": "^10.4.22",
|
||||
"cross-env": "^10.1.0",
|
||||
"electron": "40.0.0",
|
||||
"electron-builder": "^26.8.1",
|
||||
"electron-builder": "^26.4.0",
|
||||
"electron-vite": "^5.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jsdom": "^27.3.0",
|
||||
"lint-staged": "^16.4.0",
|
||||
"postcss": "^8.5.8",
|
||||
"tailwindcss": "^4.2.1",
|
||||
"lint-staged": "^16.2.7",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.2.7",
|
||||
"vitest": "^4.1.0"
|
||||
"vitest": "^4.0.16"
|
||||
},
|
||||
"overrides": {
|
||||
"electron-builder-squirrel-windows": "^26.0.12",
|
||||
"dmg-builder": "^26.0.12",
|
||||
"@electron/rebuild": "4.0.3"
|
||||
"@electron/rebuild": "4.0.2"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.aperant.app",
|
||||
"productName": "Aperant",
|
||||
"appId": "com.autoclaude.ui",
|
||||
"productName": "Auto-Claude",
|
||||
"npmRebuild": false,
|
||||
"artifactName": "${productName}-${version}-${platform}-${arch}.${ext}",
|
||||
"publish": [
|
||||
{
|
||||
"provider": "github",
|
||||
"owner": "AndyMik90",
|
||||
"repo": "Aperant"
|
||||
"repo": "Auto-Claude"
|
||||
}
|
||||
],
|
||||
"directories": {
|
||||
@@ -188,22 +188,6 @@
|
||||
{
|
||||
"from": "prompts",
|
||||
"to": "prompts"
|
||||
},
|
||||
{
|
||||
"from": "../../node_modules/@libsql",
|
||||
"to": "node_modules/@libsql"
|
||||
},
|
||||
{
|
||||
"from": "../../node_modules/libsql",
|
||||
"to": "node_modules/libsql"
|
||||
},
|
||||
{
|
||||
"from": "../../node_modules/@neon-rs",
|
||||
"to": "node_modules/@neon-rs"
|
||||
},
|
||||
{
|
||||
"from": "../../node_modules/detect-libc",
|
||||
"to": "node_modules/detect-libc"
|
||||
}
|
||||
],
|
||||
"mac": {
|
||||
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 275 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 236 KiB After Width: | Height: | Size: 20 MiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 455 B After Width: | Height: | Size: 921 B |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 290 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 6.9 KiB |
@@ -302,7 +302,7 @@ describe('Application Logger', () => {
|
||||
const { generateDebugReport } = await import('../app-logger');
|
||||
const report = generateDebugReport();
|
||||
|
||||
expect(report).toContain('=== Aperant Debug Report ===');
|
||||
expect(report).toContain('=== Auto Claude Debug Report ===');
|
||||
expect(report).toContain('--- System Information ---');
|
||||
expect(report).toContain('--- Recent Errors ---');
|
||||
expect(report).toContain('=== End Debug Report ===');
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
/**
|
||||
* Tests for ensureOnboardingComplete function in cli-integration-handler.ts
|
||||
*
|
||||
* Tests the exported ensureOnboardingComplete() which reads/writes .claude.json
|
||||
* to set hasCompletedOnboarding: true, suppressing Claude's onboarding wizard
|
||||
* for already-authenticated profiles.
|
||||
*/
|
||||
|
||||
import { describe, test, expect, vi, beforeEach } from 'vitest';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
|
||||
// ---- fs mock (sync only — the function uses fs, not fs/promises) ----
|
||||
const mockFiles: Map<string, string | Error> = new Map();
|
||||
|
||||
vi.mock('fs', () => {
|
||||
const readFileSync = vi.fn((filePath: string, _encoding?: string): string => {
|
||||
const entry = mockFiles.get(filePath);
|
||||
if (entry === undefined) {
|
||||
const err = new Error(`ENOENT: no such file or directory, open '${filePath}'`) as NodeJS.ErrnoException;
|
||||
err.code = 'ENOENT';
|
||||
throw err;
|
||||
}
|
||||
if (entry instanceof Error) {
|
||||
throw entry;
|
||||
}
|
||||
return entry;
|
||||
});
|
||||
|
||||
const writeFileSync = vi.fn();
|
||||
const renameSync = vi.fn();
|
||||
|
||||
return { default: { readFileSync, writeFileSync, renameSync }, readFileSync, writeFileSync, renameSync };
|
||||
});
|
||||
|
||||
// ---- stubs for heavy transitive dependencies ----
|
||||
vi.mock('electron', () => ({
|
||||
ipcMain: { handle: vi.fn() },
|
||||
app: { getPath: vi.fn(() => os.tmpdir()), getAppPath: vi.fn(() => os.tmpdir()) },
|
||||
dialog: { showOpenDialog: vi.fn() },
|
||||
shell: { openExternal: vi.fn() },
|
||||
}));
|
||||
|
||||
vi.mock('@electron-toolkit/utils', () => ({ is: { dev: true } }));
|
||||
|
||||
vi.mock('../../shared/constants', async () => {
|
||||
const actual = await vi.importActual<typeof import('../../shared/constants')>('../../shared/constants');
|
||||
return { ...actual };
|
||||
});
|
||||
|
||||
vi.mock('../claude-profile-manager', () => ({
|
||||
getClaudeProfileManager: vi.fn(),
|
||||
initializeClaudeProfileManager: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../claude-profile/credential-utils', () => ({
|
||||
getFullCredentialsFromKeychain: vi.fn(),
|
||||
clearKeychainCache: vi.fn(),
|
||||
updateProfileSubscriptionMetadata: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../claude-profile/usage-monitor', () => ({
|
||||
getUsageMonitor: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../claude-profile/profile-utils', () => ({
|
||||
getEmailFromConfigDir: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../terminal/output-parser', () => ({}));
|
||||
vi.mock('../terminal/session-handler', () => ({}));
|
||||
|
||||
vi.mock('./pty-manager', () => ({
|
||||
writeToPty: vi.fn(),
|
||||
resizePty: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../ipc-handlers/utils', () => ({
|
||||
safeSendToRenderer: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../shared/utils/debug-logger', () => ({
|
||||
debugLog: vi.fn(),
|
||||
debugError: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../shared/utils/shell-escape', () => ({
|
||||
escapeShellArg: vi.fn((s: string) => s),
|
||||
escapeForWindowsDoubleQuote: vi.fn((s: string) => s),
|
||||
buildCdCommand: vi.fn((cwd: string) => `cd ${cwd}`),
|
||||
}));
|
||||
|
||||
vi.mock('../cli-utils', () => ({
|
||||
getClaudeCliInvocation: vi.fn(() => 'claude'),
|
||||
getClaudeCliInvocationAsync: vi.fn(async () => 'claude'),
|
||||
}));
|
||||
|
||||
vi.mock('../platform', () => ({
|
||||
isWindows: vi.fn(() => false),
|
||||
}));
|
||||
|
||||
vi.mock('../settings-utils', () => ({
|
||||
readSettingsFileAsync: vi.fn(async () => ({})),
|
||||
readSettingsFile: vi.fn(() => ({})),
|
||||
}));
|
||||
|
||||
// ---- import the function under test ----
|
||||
import { ensureOnboardingComplete } from '../terminal/cli-integration-handler';
|
||||
import * as fs from 'fs';
|
||||
|
||||
// ---- helpers ----
|
||||
function claudeJsonPath(configDir: string): string {
|
||||
const expanded = configDir.startsWith('~')
|
||||
? configDir.replace(/^~/, os.homedir())
|
||||
: configDir;
|
||||
return path.join(path.resolve(expanded), '.claude.json');
|
||||
}
|
||||
|
||||
const TEST_DIR = '/tmp/test-profile';
|
||||
|
||||
describe('ensureOnboardingComplete', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockFiles.clear();
|
||||
});
|
||||
|
||||
// ---- ENOENT: file does not exist ----
|
||||
test('returns early (no write) when .claude.json does not exist', () => {
|
||||
// mockFiles is empty → readFileSync will throw ENOENT
|
||||
ensureOnboardingComplete(TEST_DIR);
|
||||
|
||||
expect(fs.writeFileSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// ---- already set ----
|
||||
test('returns early (no write) when hasCompletedOnboarding is already true', () => {
|
||||
const filePath = claudeJsonPath(TEST_DIR);
|
||||
mockFiles.set(filePath, JSON.stringify({ hasCompletedOnboarding: true }));
|
||||
|
||||
ensureOnboardingComplete(TEST_DIR);
|
||||
|
||||
expect(fs.writeFileSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// ---- missing flag → should write ----
|
||||
test('writes hasCompletedOnboarding: true when flag is absent', () => {
|
||||
const filePath = claudeJsonPath(TEST_DIR);
|
||||
mockFiles.set(filePath, JSON.stringify({ someOtherField: 'value' }));
|
||||
|
||||
ensureOnboardingComplete(TEST_DIR);
|
||||
|
||||
expect(fs.writeFileSync).toHaveBeenCalledOnce();
|
||||
const written = JSON.parse((fs.writeFileSync as ReturnType<typeof vi.fn>).mock.calls[0][1] as string);
|
||||
expect(written.hasCompletedOnboarding).toBe(true);
|
||||
expect(written.someOtherField).toBe('value');
|
||||
});
|
||||
|
||||
// ---- flag is false → should write ----
|
||||
test('writes hasCompletedOnboarding: true when flag is false', () => {
|
||||
const filePath = claudeJsonPath(TEST_DIR);
|
||||
mockFiles.set(filePath, JSON.stringify({ hasCompletedOnboarding: false }));
|
||||
|
||||
ensureOnboardingComplete(TEST_DIR);
|
||||
|
||||
expect(fs.writeFileSync).toHaveBeenCalledOnce();
|
||||
const written = JSON.parse((fs.writeFileSync as ReturnType<typeof vi.fn>).mock.calls[0][1] as string);
|
||||
expect(written.hasCompletedOnboarding).toBe(true);
|
||||
});
|
||||
|
||||
// ---- non-object JSON (string) → should return silently ----
|
||||
test('returns early (no write) when .claude.json contains a JSON string', () => {
|
||||
const filePath = claudeJsonPath(TEST_DIR);
|
||||
mockFiles.set(filePath, JSON.stringify('just a string'));
|
||||
|
||||
ensureOnboardingComplete(TEST_DIR);
|
||||
|
||||
expect(fs.writeFileSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// ---- array JSON → should return silently ----
|
||||
test('returns early (no write) when .claude.json contains a JSON array', () => {
|
||||
const filePath = claudeJsonPath(TEST_DIR);
|
||||
mockFiles.set(filePath, JSON.stringify([1, 2, 3]));
|
||||
|
||||
ensureOnboardingComplete(TEST_DIR);
|
||||
|
||||
expect(fs.writeFileSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// ---- corrupted / invalid JSON → outer catch swallows error ----
|
||||
test('handles corrupted JSON gracefully without throwing', () => {
|
||||
const filePath = claudeJsonPath(TEST_DIR);
|
||||
mockFiles.set(filePath, '{ invalid json }');
|
||||
|
||||
expect(() => ensureOnboardingComplete(TEST_DIR)).not.toThrow();
|
||||
expect(fs.writeFileSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// ---- tilde expansion ----
|
||||
test('expands leading tilde to home directory', () => {
|
||||
const tildeDir = '~/myprofile';
|
||||
const resolvedDir = path.resolve(tildeDir.replace(/^~/, os.homedir()));
|
||||
const filePath = path.join(resolvedDir, '.claude.json');
|
||||
|
||||
mockFiles.set(filePath, JSON.stringify({}));
|
||||
|
||||
ensureOnboardingComplete(tildeDir);
|
||||
|
||||
expect(fs.writeFileSync).toHaveBeenCalledOnce();
|
||||
// Writes to a temp file (claudeJsonPath + UUID + .tmp), then renames to target
|
||||
const writtenPath = (fs.writeFileSync as ReturnType<typeof vi.fn>).mock.calls[0][0] as string;
|
||||
expect(writtenPath).toMatch(new RegExp(`^${filePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\..*\\.tmp$`));
|
||||
expect(fs.renameSync).toHaveBeenCalledWith(writtenPath, filePath);
|
||||
});
|
||||
|
||||
// ---- write error → outer catch swallows error ----
|
||||
test('handles write error gracefully without throwing', () => {
|
||||
const filePath = claudeJsonPath(TEST_DIR);
|
||||
mockFiles.set(filePath, JSON.stringify({}));
|
||||
|
||||
(fs.writeFileSync as ReturnType<typeof vi.fn>).mockImplementationOnce(() => {
|
||||
throw new Error('EACCES: permission denied');
|
||||
});
|
||||
|
||||
expect(() => ensureOnboardingComplete(TEST_DIR)).not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -40,8 +40,8 @@ const __dirname = path.dirname(__filename);
|
||||
*/
|
||||
function resolveWorkerPath(): string {
|
||||
if (app.isPackaged) {
|
||||
// Production: worker is inside app.asar at out/main/ai/agent/worker.js
|
||||
return path.join(process.resourcesPath, 'app.asar', 'out', 'main', 'ai', 'agent', 'worker.js');
|
||||
// Production: worker is bundled alongside other main-process code
|
||||
return path.join(process.resourcesPath, 'app', 'main', 'ai', 'agent', 'worker.js');
|
||||
}
|
||||
// Dev: electron-vite outputs worker at out/main/ai/agent/worker.js
|
||||
// because the Rollup input key is 'ai/agent/worker'.
|
||||
|
||||
@@ -253,7 +253,7 @@ export async function startCodexOAuthFlow(): Promise<CodexAuthResult> {
|
||||
<body style="font-family: system-ui, sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #1a1a1a; color: #e0e0e0;">
|
||||
<div style="text-align: center;">
|
||||
<h2 style="color: #4ade80;">Authentication successful!</h2>
|
||||
<p>You can close this tab and return to Aperant.</p>
|
||||
<p>You can close this tab and return to Auto Claude.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
@@ -147,7 +147,6 @@ export const MODEL_PROVIDER_MAP: Record<string, SupportedProvider> = {
|
||||
'llama-': 'groq',
|
||||
'grok-': 'xai',
|
||||
'glm-': 'zai',
|
||||
'mascarade-': 'mascarade',
|
||||
} as const;
|
||||
|
||||
// ============================================
|
||||
|
||||
@@ -106,7 +106,7 @@ const PUPPETEER_SERVER: McpServerConfig = {
|
||||
function createAutoClaudeServer(specDir: string): McpServerConfig {
|
||||
return {
|
||||
id: 'auto-claude',
|
||||
name: 'Aperant',
|
||||
name: 'Auto-Claude',
|
||||
description: 'Build management tools (progress tracking, session context)',
|
||||
enabledByDefault: true,
|
||||
transport: {
|
||||
|
||||
@@ -7,51 +7,11 @@
|
||||
* 3. Web app (Next.js SaaS) — pure cloud libSQL
|
||||
*/
|
||||
|
||||
import type { Client, Config } from '@libsql/client/sqlite3';
|
||||
import { createRequire } from 'module';
|
||||
import { createClient } from '@libsql/client';
|
||||
import type { Client } from '@libsql/client';
|
||||
import { join } from 'path';
|
||||
import { MEMORY_SCHEMA_SQL, MEMORY_PRAGMA_SQL } from './schema';
|
||||
|
||||
/**
|
||||
* Lazy-load @libsql/client via CJS require().
|
||||
*
|
||||
* @libsql/client depends on native platform-specific modules (@libsql/darwin-arm64,
|
||||
* @libsql/linux-x64-gnu, etc.). In packaged Electron apps these live in
|
||||
* Resources/node_modules/ (via extraResources). ESM import() can't resolve them
|
||||
* from within app.asar, but CJS require() works because Module.globalPaths is
|
||||
* patched at startup in index.ts to include Resources/node_modules/.
|
||||
*
|
||||
* Using a lazy getter avoids a static import that would crash at startup before
|
||||
* the globalPaths patch runs.
|
||||
*/
|
||||
let _createClient: ((config: Config) => Client) | null = null;
|
||||
|
||||
function loadCreateClient(): (config: Config) => Client {
|
||||
if (!_createClient) {
|
||||
// In Electron: globalThis.require is set up in index.ts with Module.globalPaths
|
||||
// patched to include Resources/node_modules/ for extraResources packages.
|
||||
// In tests/dev: fall back to createRequire (deps are in normal node_modules).
|
||||
const req = globalThis.require ?? createRequire(import.meta.url);
|
||||
let mod: Record<string, unknown>;
|
||||
try {
|
||||
mod = req('@libsql/client/sqlite3');
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Failed to load @libsql/client/sqlite3: ${(err as Error).message}. ` +
|
||||
`Ensure native modules are available in Resources/node_modules/`
|
||||
);
|
||||
}
|
||||
if (typeof mod.createClient !== 'function') {
|
||||
throw new Error(
|
||||
`@libsql/client/sqlite3 did not export createClient (got ${typeof mod.createClient}). ` +
|
||||
`Check that native modules are available in Resources/node_modules/`
|
||||
);
|
||||
}
|
||||
_createClient = mod.createClient as (config: Config) => Client;
|
||||
}
|
||||
return _createClient!;
|
||||
}
|
||||
|
||||
let _client: Client | null = null;
|
||||
|
||||
/**
|
||||
@@ -71,7 +31,7 @@ export async function getMemoryClient(
|
||||
const { app } = await import('electron');
|
||||
const localPath = join(app.getPath('userData'), 'memory.db');
|
||||
|
||||
_client = loadCreateClient()({
|
||||
_client = createClient({
|
||||
url: `file:${localPath}`,
|
||||
...(tursoSyncUrl && authToken
|
||||
? { syncUrl: tursoSyncUrl, authToken, syncInterval: 60 }
|
||||
@@ -118,7 +78,7 @@ export async function getWebMemoryClient(
|
||||
tursoUrl: string,
|
||||
authToken: string,
|
||||
): Promise<Client> {
|
||||
const client = loadCreateClient()({ url: tursoUrl, authToken });
|
||||
const client = createClient({ url: tursoUrl, authToken });
|
||||
|
||||
// Apply PRAGMAs
|
||||
for (const pragma of MEMORY_PRAGMA_SQL.split('\n').filter(l => l.trim())) {
|
||||
@@ -137,7 +97,7 @@ export async function getWebMemoryClient(
|
||||
* Create an in-memory client (for tests — no Electron dependency).
|
||||
*/
|
||||
export async function getInMemoryClient(): Promise<Client> {
|
||||
const client = loadCreateClient()({ url: ':memory:' });
|
||||
const client = createClient({ url: ':memory:' });
|
||||
await client.executeMultiple(MEMORY_SCHEMA_SQL);
|
||||
return client;
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ export interface MemoryMethodologyPlugin {
|
||||
|
||||
export const nativePlugin: MemoryMethodologyPlugin = {
|
||||
id: 'native',
|
||||
displayName: 'Aperant (Subtasks)',
|
||||
displayName: 'Auto Claude (Subtasks)',
|
||||
mapPhase: (p: string): UniversalPhase => {
|
||||
const map: Record<string, UniversalPhase> = {
|
||||
planning: 'define',
|
||||
|
||||
@@ -156,24 +156,6 @@ function createProviderInstance(config: ProviderConfig) {
|
||||
});
|
||||
}
|
||||
|
||||
case SupportedProvider.Mascarade: {
|
||||
// Mascarade LLM orchestration engine — OpenAI-compatible API
|
||||
// Default: http://localhost:8100/v1 (Tower) or configurable
|
||||
let mascaradeBaseURL = baseURL ?? 'http://localhost:8100/v1';
|
||||
if (!mascaradeBaseURL.endsWith('/v1')) {
|
||||
mascaradeBaseURL = mascaradeBaseURL.replace(/\/+$/, '') + '/v1';
|
||||
}
|
||||
return createOpenAICompatible({
|
||||
name: 'mascarade',
|
||||
apiKey: apiKey ?? 'mascarade-local',
|
||||
baseURL: mascaradeBaseURL,
|
||||
headers: {
|
||||
...headers,
|
||||
'Authorization': `Bearer ${apiKey ?? 'mascarade-local'}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
default: {
|
||||
const _exhaustive: never = provider;
|
||||
throw new Error(`Unsupported provider: ${_exhaustive}`);
|
||||
|
||||
@@ -21,7 +21,6 @@ export const SupportedProvider = {
|
||||
OpenRouter: 'openrouter',
|
||||
ZAI: 'zai',
|
||||
Ollama: 'ollama',
|
||||
Mascarade: 'mascarade',
|
||||
} as const;
|
||||
|
||||
export type SupportedProvider = (typeof SupportedProvider)[keyof typeof SupportedProvider];
|
||||
|
||||
@@ -382,7 +382,7 @@ ${diffContent}
|
||||
}
|
||||
|
||||
lines.push('---');
|
||||
lines.push('_Generated by Aperant MR Review_');
|
||||
lines.push('_Generated by Auto Claude MR Review_');
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ const BLOCKED_PROCESS_NAMES = new Set([
|
||||
'electron',
|
||||
'Electron',
|
||||
'auto-claude',
|
||||
'Aperant',
|
||||
'Auto Claude',
|
||||
]);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -149,7 +149,7 @@ export function generateDebugReport(): string {
|
||||
const recentErrors = getRecentErrors(10);
|
||||
|
||||
const lines = [
|
||||
'=== Aperant Debug Report ===',
|
||||
'=== Auto Claude Debug Report ===',
|
||||
`Generated: ${new Date().toISOString()}`,
|
||||
'',
|
||||
'--- System Information ---',
|
||||
|
||||
@@ -30,7 +30,7 @@ import { isMacOS } from './platform';
|
||||
|
||||
// GitHub repo info for API calls
|
||||
const GITHUB_OWNER = 'AndyMik90';
|
||||
const GITHUB_REPO = 'Aperant';
|
||||
const GITHUB_REPO = 'Auto-Claude';
|
||||
|
||||
// Debug mode - DEBUG_UPDATER=true or development mode
|
||||
const DEBUG_UPDATER = process.env.DEBUG_UPDATER === 'true' || process.env.NODE_ENV === 'development';
|
||||
@@ -488,7 +488,7 @@ async function fetchLatestStableRelease(): Promise<AppUpdateInfo | null> {
|
||||
});
|
||||
|
||||
request.setHeader('Accept', 'application/vnd.github.v3+json');
|
||||
request.setHeader('User-Agent', `Aperant/${getCurrentVersion()}`);
|
||||
request.setHeader('User-Agent', `Auto-Claude/${getCurrentVersion()}`);
|
||||
|
||||
let data = '';
|
||||
|
||||
|
||||
@@ -3,24 +3,11 @@
|
||||
// require-in-the-middle hooks. Sentry's hooks expect require.cache to exist,
|
||||
// which is only available in CommonJS. Without this, node-pty native module
|
||||
// loading fails with "ReferenceError: require is not defined".
|
||||
import Module, { createRequire } from 'module';
|
||||
import { createRequire } from 'module';
|
||||
const require = createRequire(import.meta.url);
|
||||
// Make require globally available for Sentry's require-in-the-middle hooks
|
||||
globalThis.require = require;
|
||||
|
||||
// In packaged Electron apps, native modules (e.g. @libsql/client) are placed in
|
||||
// Resources/node_modules/ via extraResources. Add that path to CJS resolution so
|
||||
// globalThis.require() can find them at runtime.
|
||||
if (process.resourcesPath) {
|
||||
const nativeModulesPath = require('path').join(process.resourcesPath, 'node_modules');
|
||||
// Module.globalPaths is an undocumented but stable Node.js internal used for
|
||||
// CJS module resolution. It's not in @types/node, hence the cast.
|
||||
const globalPaths = (Module as unknown as { globalPaths: string[] }).globalPaths;
|
||||
if (!globalPaths.includes(nativeModulesPath)) {
|
||||
globalPaths.push(nativeModulesPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Load .env file FIRST before any other imports that might use process.env
|
||||
import { config } from 'dotenv';
|
||||
import { resolve, dirname } from 'path';
|
||||
@@ -50,7 +37,7 @@ for (const envPath of possibleEnvPaths) {
|
||||
|
||||
import { app, BrowserWindow, shell, nativeImage, session, screen, Menu, MenuItem } from 'electron';
|
||||
import { join } from 'path';
|
||||
import { accessSync, readFileSync, writeFileSync, rmSync, cpSync } from 'fs';
|
||||
import { accessSync, readFileSync, writeFileSync, rmSync } from 'fs';
|
||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils';
|
||||
import { setupIpcHandlers } from './ipc-setup';
|
||||
import { AgentManager } from './agent';
|
||||
@@ -71,26 +58,6 @@ import { isMacOS, isWindows } from './platform';
|
||||
import { ptyDaemonClient } from './terminal/pty-daemon-client';
|
||||
import type { AppSettings, AuthFailureInfo } from '../shared/types';
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Migrate userData from old app name (auto-claude-ui → aperant)
|
||||
// Must run before any code accesses app.getPath('userData')
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
{
|
||||
const newUserData = app.getPath('userData');
|
||||
const oldUserData = join(dirname(newUserData), 'auto-claude-ui');
|
||||
if (existsSync(oldUserData) && !existsSync(join(newUserData, '.migrated'))) {
|
||||
try {
|
||||
// Copy all files from old location to new (don't move — keeps old as backup)
|
||||
cpSync(oldUserData, newUserData, { recursive: true, force: false, errorOnExist: false });
|
||||
// Mark as migrated so we don't repeat
|
||||
writeFileSync(join(newUserData, '.migrated'), new Date().toISOString());
|
||||
console.warn('[main] Migrated userData from auto-claude-ui to aperant');
|
||||
} catch (err) {
|
||||
console.warn('[main] userData migration failed (non-fatal):', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Window sizing constants
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
@@ -406,7 +373,7 @@ if (isWindows()) {
|
||||
// Initialize the application
|
||||
app.whenReady().then(() => {
|
||||
// Set app user model id for Windows
|
||||
electronApp.setAppUserModelId('com.aperant.app');
|
||||
electronApp.setAppUserModelId('com.autoclaude.ui');
|
||||
|
||||
// Clear cache on Windows to prevent permission errors from stale cache
|
||||
if (isWindows()) {
|
||||
|
||||
@@ -129,7 +129,7 @@ async function githubGraphQL<T>(
|
||||
headers: {
|
||||
"Authorization": `Bearer ${safeToken}`,
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Aperant",
|
||||
"User-Agent": "Auto-Claude-UI",
|
||||
},
|
||||
body: JSON.stringify({ query, variables }),
|
||||
});
|
||||
@@ -2275,7 +2275,7 @@ export function registerPRHandlers(getMainWindow: () => BrowserWindow | null): v
|
||||
|
||||
if (options?.forceApprove) {
|
||||
// Auto-approve format: clean approval message with optional suggestions
|
||||
body = `## ✅ Aperant Review - APPROVED\n\n`;
|
||||
body = `## ✅ Auto Claude Review - APPROVED\n\n`;
|
||||
body += `**Status:** Ready to Merge\n\n`;
|
||||
body += `**Summary:** ${result.summary}\n\n`;
|
||||
|
||||
@@ -2298,10 +2298,10 @@ export function registerPRHandlers(getMainWindow: () => BrowserWindow | null): v
|
||||
}
|
||||
|
||||
body += `---\n*This automated review found no blocking issues. The PR can be safely merged.*\n\n`;
|
||||
body += `*Generated by Aperant*`;
|
||||
body += `*Generated by Auto Claude*`;
|
||||
} else {
|
||||
// Standard review format
|
||||
body = `## 🤖 Aperant PR Review\n\n${result.summary}\n\n`;
|
||||
body = `## 🤖 Auto Claude PR Review\n\n${result.summary}\n\n`;
|
||||
|
||||
if (findings.length > 0) {
|
||||
// Show selected count vs total if filtered
|
||||
@@ -2326,7 +2326,7 @@ export function registerPRHandlers(getMainWindow: () => BrowserWindow | null): v
|
||||
body += `*No findings selected for this review.*\n\n`;
|
||||
}
|
||||
|
||||
body += `---\n*This review was generated by Aperant.*`;
|
||||
body += `---\n*This review was generated by Auto Claude.*`;
|
||||
}
|
||||
|
||||
// Determine review status based on selected findings (or force approve)
|
||||
|
||||
@@ -267,7 +267,7 @@ export async function githubFetch(
|
||||
headers: {
|
||||
'Accept': 'application/vnd.github+json',
|
||||
'Authorization': `Bearer ${safeToken}`,
|
||||
'User-Agent': 'Aperant',
|
||||
'User-Agent': 'Auto-Claude-UI',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
@@ -298,7 +298,7 @@ export async function githubFetchWithETag(
|
||||
const headers: Record<string, string> = {
|
||||
'Accept': 'application/vnd.github+json',
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'User-Agent': 'Aperant'
|
||||
'User-Agent': 'Auto-Claude-UI'
|
||||
};
|
||||
|
||||
// Add If-None-Match header if we have a cached ETag
|
||||
|
||||
@@ -108,7 +108,7 @@ function formatReviewBody(result: MRReviewResult, selectedFindingIds?: string[])
|
||||
? result.findings.filter(f => selectedSet.has(f.id))
|
||||
: result.findings;
|
||||
|
||||
let body = `## Aperant MR Review\n\n${result.summary}\n\n`;
|
||||
let body = `## Auto Claude MR Review\n\n${result.summary}\n\n`;
|
||||
|
||||
if (findings.length > 0) {
|
||||
const countText = selectedSet
|
||||
@@ -130,7 +130,7 @@ function formatReviewBody(result: MRReviewResult, selectedFindingIds?: string[])
|
||||
body += `*No findings selected for this review.*\n\n`;
|
||||
}
|
||||
|
||||
body += `---\n*This review was generated by Aperant.*`;
|
||||
body += `---\n*This review was generated by Auto Claude.*`;
|
||||
|
||||
return body;
|
||||
}
|
||||
@@ -335,7 +335,7 @@ describe('GitLab MR Review Handlers', () => {
|
||||
it('should format review header', () => {
|
||||
const body = formatReviewBody(baseResult);
|
||||
|
||||
expect(body).toContain('## Aperant MR Review');
|
||||
expect(body).toContain('## Auto Claude MR Review');
|
||||
expect(body).toContain('Found 2 issues that need attention');
|
||||
});
|
||||
|
||||
@@ -410,7 +410,7 @@ describe('GitLab MR Review Handlers', () => {
|
||||
const body = formatReviewBody(baseResult);
|
||||
|
||||
expect(body).toContain('---');
|
||||
expect(body).toContain('*This review was generated by Aperant.*');
|
||||
expect(body).toContain('*This review was generated by Auto Claude.*');
|
||||
});
|
||||
|
||||
it('should format finding descriptions', () => {
|
||||
|
||||
@@ -520,7 +520,7 @@ export function registerMRReviewHandlers(
|
||||
debugLog('Posting findings', { total: result.findings.length, selected: findings.length });
|
||||
|
||||
// Build note body
|
||||
let body = `## Aperant MR Review\n\n${result.summary}\n\n`;
|
||||
let body = `## Auto Claude MR Review\n\n${result.summary}\n\n`;
|
||||
|
||||
if (findings.length > 0) {
|
||||
const countText = selectedSet
|
||||
@@ -542,7 +542,7 @@ export function registerMRReviewHandlers(
|
||||
body += `*No findings selected for this review.*\n\n`;
|
||||
}
|
||||
|
||||
body += `---\n*This review was generated by Aperant.*`;
|
||||
body += `---\n*This review was generated by Auto Claude.*`;
|
||||
|
||||
const encodedProject = encodeProjectPath(config.project);
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ export function registerInsightsHandlers(getMainWindow: () => BrowserWindow | nu
|
||||
}
|
||||
|
||||
if (!project.autoBuildPath) {
|
||||
return { success: false, error: "Aperant not initialized for this project" };
|
||||
return { success: false, error: "Auto Claude not initialized for this project" };
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -264,7 +264,7 @@ const detectAutoBuildSourcePath = (): string | null => {
|
||||
}
|
||||
}
|
||||
|
||||
console.warn('[detectAutoBuildSourcePath] Could not auto-detect Aperant prompts path. Please configure manually in settings.');
|
||||
console.warn('[detectAutoBuildSourcePath] Could not auto-detect Auto Claude prompts path. Please configure manually in settings.');
|
||||
console.warn('[detectAutoBuildSourcePath] Set DEBUG=1 environment variable for detailed path checking.');
|
||||
return null;
|
||||
};
|
||||
@@ -933,20 +933,6 @@ export function registerSettingsHandlers(
|
||||
try {
|
||||
const settings = readSettingsFile() ?? {};
|
||||
const accounts: ProviderAccount[] = (settings.providerAccounts as ProviderAccount[] | undefined) ?? [];
|
||||
|
||||
// Prevent duplicate: same email + provider already registered
|
||||
if (account.email) {
|
||||
const duplicate = accounts.find(
|
||||
(a) => a.provider === account.provider && a.email?.toLowerCase() === account.email!.toLowerCase()
|
||||
);
|
||||
if (duplicate) {
|
||||
return {
|
||||
success: false,
|
||||
error: `DUPLICATE_EMAIL:${duplicate.name}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const newAccount: ProviderAccount = {
|
||||
...account,
|
||||
|
||||
@@ -177,7 +177,7 @@ export function registerTaskExecutionHandlers(
|
||||
mainWindow.webContents.send(
|
||||
IPC_CHANNELS.TASK_ERROR,
|
||||
taskId,
|
||||
'Git repository required. Please run "git init" in your project directory. Aperant uses git worktrees for isolated builds.'
|
||||
'Git repository required. Please run "git init" in your project directory. Auto Claude uses git worktrees for isolated builds.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -205,14 +205,14 @@ function ensureGitignoreEntries(projectPath: string, entries: string[]): void {
|
||||
appendContent += '\n';
|
||||
}
|
||||
|
||||
appendContent += '\n# Aperant data directory\n';
|
||||
appendContent += '\n# Auto Claude data directory\n';
|
||||
for (const entry of entriesToAdd) {
|
||||
appendContent += entry + '\n';
|
||||
}
|
||||
|
||||
appendFileSync(gitignorePath, appendContent);
|
||||
} else {
|
||||
writeFileSync(gitignorePath, '# Aperant data directory\n' + entriesToAdd.join('\n') + '\n', 'utf-8');
|
||||
writeFileSync(gitignorePath, '# Auto Claude data directory\n' + entriesToAdd.join('\n') + '\n', 'utf-8');
|
||||
}
|
||||
|
||||
debug('Added entries to .gitignore', { entries: entriesToAdd });
|
||||
@@ -288,13 +288,13 @@ export function initializeProject(projectPath: string): InitializationResult {
|
||||
};
|
||||
}
|
||||
|
||||
// Check git status - Aperant requires git for worktree-based builds
|
||||
// Check git status - Auto Claude requires git for worktree-based builds
|
||||
const gitStatus = checkGitStatus(projectPath);
|
||||
if (!gitStatus.isGitRepo || !gitStatus.hasCommits) {
|
||||
debug('Git check failed', { gitStatus });
|
||||
return {
|
||||
success: false,
|
||||
error: gitStatus.error || 'Git repository required. Aperant uses git worktrees for isolated builds.'
|
||||
error: gitStatus.error || 'Git repository required. Auto Claude uses git worktrees for isolated builds.'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ export function ensureDataDirectories(projectPath: string): InitializationResult
|
||||
*
|
||||
* IMPORTANT: Only .auto-claude/ is considered a valid "installed" auto-claude.
|
||||
* The auto-claude/ folder (if it exists) is the SOURCE CODE being developed,
|
||||
* not an installation. This allows Aperant to be used to develop itself.
|
||||
* not an installation. This allows Auto Claude to be used to develop itself.
|
||||
*/
|
||||
export function getAutoBuildPath(projectPath: string): string | null {
|
||||
const dotAutoBuildPath = path.join(projectPath, '.auto-claude');
|
||||
|
||||
@@ -27,7 +27,8 @@ import type {
|
||||
TerminalProcess,
|
||||
WindowGetter,
|
||||
RateLimitEvent,
|
||||
OAuthTokenEvent
|
||||
OAuthTokenEvent,
|
||||
OnboardingCompleteEvent
|
||||
} from './types';
|
||||
|
||||
// ============================================================================
|
||||
@@ -563,17 +564,17 @@ export function handleOAuthToken(
|
||||
|
||||
console.warn('[ClaudeIntegration] Profile credentials verified via Keychain (not caching token):', profileId);
|
||||
|
||||
// Mark onboarding complete so future `claude` invocations skip the wizard.
|
||||
// `claude auth login` creates .claude.json but doesn't set this flag.
|
||||
if (profile.configDir) {
|
||||
ensureOnboardingComplete(profile.configDir);
|
||||
}
|
||||
// Set flag to watch for Claude's ready state (onboarding complete)
|
||||
terminal.awaitingOnboardingComplete = true;
|
||||
|
||||
// needsOnboarding: true tells the UI to show "complete setup" message
|
||||
// instead of "success" - user should finish Claude's onboarding before closing
|
||||
safeSendToRenderer(getWindow, IPC_CHANNELS.TERMINAL_OAUTH_TOKEN, {
|
||||
terminalId: terminal.id,
|
||||
profileId,
|
||||
email: emailFromOutput || keychainCreds.email || profile?.email,
|
||||
success: true,
|
||||
needsOnboarding: true,
|
||||
detectedAt: new Date().toISOString()
|
||||
} as OAuthTokenEvent);
|
||||
} else {
|
||||
@@ -584,16 +585,17 @@ export function handleOAuthToken(
|
||||
if (hasCredentials) {
|
||||
console.warn('[ClaudeIntegration] Profile credentials verified (no Keychain token):', profileId);
|
||||
|
||||
// Mark onboarding complete so future `claude` invocations skip the wizard
|
||||
if (profile.configDir) {
|
||||
ensureOnboardingComplete(profile.configDir);
|
||||
}
|
||||
// Set flag to watch for Claude's ready state (onboarding complete)
|
||||
terminal.awaitingOnboardingComplete = true;
|
||||
|
||||
// needsOnboarding: true tells the UI to show "complete setup" message
|
||||
// instead of "success" - user should finish Claude's onboarding before closing
|
||||
safeSendToRenderer(getWindow, IPC_CHANNELS.TERMINAL_OAUTH_TOKEN, {
|
||||
terminalId: terminal.id,
|
||||
profileId,
|
||||
email: emailFromOutput || profile?.email,
|
||||
success: true,
|
||||
needsOnboarding: true,
|
||||
detectedAt: new Date().toISOString()
|
||||
} as OAuthTokenEvent);
|
||||
} else {
|
||||
@@ -716,19 +718,130 @@ export function handleOAuthToken(
|
||||
|
||||
/**
|
||||
* Handle onboarding complete detection
|
||||
* Called when terminal output indicates Claude Code is ready after login/onboarding.
|
||||
* Called when terminal output indicates Claude Code is ready after login/onboarding
|
||||
*
|
||||
* Note: This is now a no-op. The onboarding flag is set proactively via
|
||||
* ensureOnboardingComplete() in handleOAuthToken() and executeProfileCommand(),
|
||||
* so awaitingOnboardingComplete is never set and this path is never reached.
|
||||
* Kept as a stub to satisfy the terminal-event-handler callback interface.
|
||||
* This detects the Claude Code welcome screen that appears after successful login,
|
||||
* which includes patterns like "Welcome back", "Claude Code v2.x", or subscription
|
||||
* tier info like "Claude Max". When detected, it notifies the frontend to auto-close
|
||||
* the auth terminal.
|
||||
*/
|
||||
export function handleOnboardingComplete(
|
||||
_terminal: TerminalProcess,
|
||||
_data: string,
|
||||
_getWindow: WindowGetter
|
||||
terminal: TerminalProcess,
|
||||
data: string,
|
||||
getWindow: WindowGetter
|
||||
): void {
|
||||
// No-op — onboarding is handled proactively in handleOAuthToken()
|
||||
// Only check if we're waiting for onboarding to complete
|
||||
if (!terminal.awaitingOnboardingComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if output shows Claude Code welcome screen (onboarding complete indicators)
|
||||
if (!OutputParser.isOnboardingCompleteOutput(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn('[ClaudeIntegration] Onboarding complete detected for terminal:', terminal.id);
|
||||
|
||||
// Clear the flag
|
||||
terminal.awaitingOnboardingComplete = false;
|
||||
|
||||
// Extract profile ID from terminal ID pattern (claude-login-{profileId}-*)
|
||||
const profileId = extractProfileIdFromAuthTerminalId(terminal.id) || undefined;
|
||||
|
||||
// Try to extract email from the welcome screen (e.g., "user@example.com's Organization")
|
||||
// Note: extractEmail automatically strips ANSI escape codes internally
|
||||
let email = OutputParser.extractEmail(data);
|
||||
if (!email) {
|
||||
email = OutputParser.extractEmail(terminal.outputBuffer);
|
||||
}
|
||||
|
||||
// Fallback: If terminal extraction failed or might be corrupt, read directly from Claude's config file
|
||||
// This is the authoritative source and doesn't suffer from ANSI escape code issues
|
||||
const profileManager = getClaudeProfileManager();
|
||||
const profile = profileId ? profileManager.getProfile(profileId) : null;
|
||||
|
||||
if (!email && profile?.configDir) {
|
||||
const configEmail = getEmailFromConfigDir(profile.configDir);
|
||||
if (configEmail) {
|
||||
console.warn('[ClaudeIntegration] Email not found in terminal output, using config file:', maskEmail(configEmail));
|
||||
email = configEmail;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate email looks correct (basic sanity check)
|
||||
// If terminal extraction gave us a truncated email but config file has the correct one, prefer config
|
||||
if (email && profile?.configDir) {
|
||||
const configEmail = getEmailFromConfigDir(profile.configDir);
|
||||
if (configEmail && configEmail !== email) {
|
||||
// Config file email is different - it's more authoritative
|
||||
console.warn('[ClaudeIntegration] Terminal email differs from config file, using config file:', {
|
||||
terminalEmail: maskEmail(email),
|
||||
configEmail: maskEmail(configEmail)
|
||||
});
|
||||
email = configEmail;
|
||||
}
|
||||
}
|
||||
|
||||
console.warn('[ClaudeIntegration] Email extraction attempt:', {
|
||||
profileId,
|
||||
foundEmail: maskEmail(email),
|
||||
dataLength: data.length,
|
||||
bufferLength: terminal.outputBuffer.length
|
||||
});
|
||||
|
||||
// Update profile with email and subscription metadata if found and profile exists
|
||||
// Always update - the newly extracted email from re-authentication should overwrite any stale/truncated email
|
||||
if (profileId && email && profile) {
|
||||
const previousEmail = profile.email;
|
||||
profile.email = email;
|
||||
// Also update subscription metadata from Keychain credentials
|
||||
updateProfileSubscriptionMetadata(profile, profile.configDir);
|
||||
profileManager.saveProfile(profile);
|
||||
if (previousEmail !== email) {
|
||||
console.warn('[ClaudeIntegration] Updated profile email from welcome screen:', profileId, maskEmail(email), '(was:', maskEmail(previousEmail), ')');
|
||||
}
|
||||
}
|
||||
|
||||
safeSendToRenderer(getWindow, IPC_CHANNELS.TERMINAL_ONBOARDING_COMPLETE, {
|
||||
terminalId: terminal.id,
|
||||
profileId,
|
||||
detectedAt: new Date().toISOString()
|
||||
} as OnboardingCompleteEvent);
|
||||
|
||||
// Trigger immediate usage fetch after successful re-authentication
|
||||
// This gives the user immediate feedback that their account is working
|
||||
if (profileId) {
|
||||
try {
|
||||
const usageMonitor = getUsageMonitor();
|
||||
if (usageMonitor) {
|
||||
// Clear any auth failure status for this profile since they just re-authenticated
|
||||
usageMonitor.clearAuthFailedProfile(profileId);
|
||||
|
||||
console.warn('[ClaudeIntegration] Triggering immediate usage fetch after re-authentication:', profileId);
|
||||
|
||||
// Switch to this profile if it's not already active, then fetch usage
|
||||
const profileManager = getClaudeProfileManager();
|
||||
|
||||
// Also clear the migration flag if this profile was migrated to an isolated directory
|
||||
// This prevents the auth failure modal from showing again on next startup
|
||||
if (profileManager.isProfileMigrated(profileId)) {
|
||||
profileManager.clearMigratedProfile(profileId);
|
||||
console.warn('[ClaudeIntegration] Cleared migration flag for re-authenticated profile:', profileId);
|
||||
}
|
||||
const activeProfile = profileManager.getActiveProfile();
|
||||
if (activeProfile?.id !== profileId) {
|
||||
profileManager.setActiveProfile(profileId);
|
||||
}
|
||||
|
||||
// Small delay to allow profile switch to settle, then trigger usage fetch
|
||||
setTimeout(() => {
|
||||
usageMonitor.checkNow();
|
||||
}, 500);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ClaudeIntegration] Failed to trigger post-auth usage fetch:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -780,54 +893,6 @@ export function handleClaudeExit(
|
||||
safeSendToRenderer(getWindow, IPC_CHANNELS.TERMINAL_CLAUDE_EXIT, terminal.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure hasCompletedOnboarding is set in profile's .claude.json.
|
||||
* When CLAUDE_CONFIG_DIR is set, Claude Code reads .claude.json from that directory.
|
||||
* Without this flag, it triggers the onboarding wizard even for authenticated profiles.
|
||||
*/
|
||||
export function ensureOnboardingComplete(configDir: string): void {
|
||||
try {
|
||||
const expandedDir = path.resolve(
|
||||
configDir.startsWith('~') ? configDir.replace(/^~/, os.homedir()) : configDir
|
||||
);
|
||||
const claudeJsonPath = path.join(expandedDir, '.claude.json');
|
||||
|
||||
// Read directly instead of existsSync + readFileSync to avoid TOCTOU race (CodeQL js/file-system-race)
|
||||
let content: string;
|
||||
try {
|
||||
content = fs.readFileSync(claudeJsonPath, 'utf-8');
|
||||
} catch (readErr) {
|
||||
if ((readErr as NodeJS.ErrnoException).code === 'ENOENT') {
|
||||
return; // No .claude.json yet — Claude Code will create it during auth
|
||||
}
|
||||
throw readErr;
|
||||
}
|
||||
|
||||
const config = JSON.parse(content);
|
||||
|
||||
if (typeof config !== 'object' || config === null || Array.isArray(config)) {
|
||||
return; // Not a valid config object
|
||||
}
|
||||
|
||||
if (config.hasCompletedOnboarding === true) {
|
||||
return; // Already set
|
||||
}
|
||||
|
||||
config.hasCompletedOnboarding = true;
|
||||
const updatedContent = JSON.stringify(config, null, 2);
|
||||
|
||||
// Write atomically via temp file + rename to avoid partial writes and satisfy CodeQL js/insecure-temporary-file.
|
||||
// crypto.randomUUID() ensures no collisions; mode 0o600 restricts to owner-only.
|
||||
const tmpPath = `${claudeJsonPath}.${crypto.randomUUID()}.tmp`;
|
||||
fs.writeFileSync(tmpPath, updatedContent, { encoding: 'utf-8', mode: 0o600 });
|
||||
fs.renameSync(tmpPath, claudeJsonPath);
|
||||
debugLog(`[ClaudeIntegration] Set hasCompletedOnboarding in ${claudeJsonPath}`);
|
||||
} catch (error) {
|
||||
// Non-fatal — worst case the user sees onboarding once
|
||||
debugError('[ClaudeIntegration] Failed to set hasCompletedOnboarding:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared command execution logic for profile-based invocation
|
||||
* Returns true if command was executed via configDir or temp-file method
|
||||
@@ -873,9 +938,6 @@ function executeProfileCommand(options: ExecuteProfileCommandOptions): boolean {
|
||||
// read full Keychain credentials including subscriptionType ("max") and rateLimitTier.
|
||||
// Using CLAUDE_CODE_OAUTH_TOKEN alone lacks tier info, causing "Claude API" display.
|
||||
if (activeProfile.configDir) {
|
||||
// Ensure Claude Code skips onboarding for authenticated profiles
|
||||
ensureOnboardingComplete(activeProfile.configDir);
|
||||
|
||||
const command = buildClaudeShellCommand(
|
||||
cwdCommand,
|
||||
pathPrefix,
|
||||
@@ -954,9 +1016,6 @@ async function executeProfileCommandAsync(options: ExecuteProfileCommandOptions)
|
||||
// read full Keychain credentials including subscriptionType ("max") and rateLimitTier.
|
||||
// Using CLAUDE_CODE_OAUTH_TOKEN alone lacks tier info, causing "Claude API" display.
|
||||
if (activeProfile.configDir) {
|
||||
// Ensure Claude Code skips onboarding for authenticated profiles
|
||||
ensureOnboardingComplete(activeProfile.configDir);
|
||||
|
||||
const command = buildClaudeShellCommand(
|
||||
cwdCommand,
|
||||
pathPrefix,
|
||||
|
||||
@@ -26,6 +26,8 @@ export interface TerminalProcess {
|
||||
dangerouslySkipPermissions?: boolean;
|
||||
/** Shell type for Windows (affects command chaining syntax) */
|
||||
shellType?: WindowsShellType;
|
||||
/** Whether this terminal is waiting for Claude onboarding to complete (login flow) */
|
||||
awaitingOnboardingComplete?: boolean;
|
||||
/** Whether PTY has emitted exit; used to avoid writes/resizes on dead PTYs */
|
||||
hasExited?: boolean;
|
||||
}
|
||||
@@ -53,8 +55,19 @@ export interface OAuthTokenEvent {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
detectedAt: string;
|
||||
/** If true, user should complete onboarding in terminal before closing */
|
||||
needsOnboarding?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Onboarding complete event data
|
||||
* Sent when Claude Code shows its ready state after login/onboarding
|
||||
*/
|
||||
export interface OnboardingCompleteEvent {
|
||||
terminalId: string;
|
||||
profileId?: string;
|
||||
detectedAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Session capture result
|
||||
|
||||
@@ -80,7 +80,7 @@ export interface TerminalAPI {
|
||||
onTerminalClaudeSession: (callback: (id: string, sessionId: string) => void) => () => void;
|
||||
onTerminalRateLimit: (callback: (info: RateLimitInfo) => void) => () => void;
|
||||
onTerminalOAuthToken: (
|
||||
callback: (info: { terminalId: string; profileId?: string; email?: string; success: boolean; message?: string; detectedAt: string }) => void
|
||||
callback: (info: { terminalId: string; profileId?: string; email?: string; success: boolean; message?: string; detectedAt: string; needsOnboarding?: boolean }) => void
|
||||
) => () => void;
|
||||
onTerminalAuthCreated: (
|
||||
callback: (info: { terminalId: string; profileId: string; profileName: string }) => void
|
||||
@@ -91,6 +91,9 @@ export interface TerminalAPI {
|
||||
submitOAuthCode: (terminalId: string, code: string) => Promise<IPCResult>;
|
||||
onTerminalClaudeBusy: (callback: (id: string, isBusy: boolean) => void) => () => void;
|
||||
onTerminalClaudeExit: (callback: (id: string) => void) => () => void;
|
||||
onTerminalOnboardingComplete: (
|
||||
callback: (info: { terminalId: string; profileId?: string; detectedAt: string }) => void
|
||||
) => () => void;
|
||||
onTerminalPendingResume: (callback: (id: string, sessionId?: string) => void) => () => void;
|
||||
onTerminalProfileChanged: (callback: (event: TerminalProfileChangedEvent) => void) => () => void;
|
||||
|
||||
@@ -385,6 +388,21 @@ export const createTerminalAPI = (): TerminalAPI => ({
|
||||
};
|
||||
},
|
||||
|
||||
onTerminalOnboardingComplete: (
|
||||
callback: (info: { terminalId: string; profileId?: string; detectedAt: string }) => void
|
||||
): (() => void) => {
|
||||
const handler = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
info: { terminalId: string; profileId?: string; detectedAt: string }
|
||||
): void => {
|
||||
callback(info);
|
||||
};
|
||||
ipcRenderer.on(IPC_CHANNELS.TERMINAL_ONBOARDING_COMPLETE, handler);
|
||||
return () => {
|
||||
ipcRenderer.removeListener(IPC_CHANNELS.TERMINAL_ONBOARDING_COMPLETE, handler);
|
||||
};
|
||||
},
|
||||
|
||||
onTerminalPendingResume: (
|
||||
callback: (id: string, sessionId?: string) => void
|
||||
): (() => void) => {
|
||||
|
||||
@@ -747,7 +747,7 @@ export function App() {
|
||||
} else {
|
||||
// Initialization failed - show error but keep dialog open
|
||||
console.warn('[InitDialog] Initialization failed, showing error');
|
||||
const errorMessage = result?.error || 'Failed to initialize Aperant. Please try again.';
|
||||
const errorMessage = result?.error || 'Failed to initialize Auto Claude. Please try again.';
|
||||
setInitError(errorMessage);
|
||||
setIsInitializing(false);
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@ const MCP_SERVERS: Record<string, { name: string; description: string; icon: Rea
|
||||
],
|
||||
},
|
||||
'auto-claude': {
|
||||
name: 'Aperant Tools',
|
||||
name: 'Auto-Claude Tools',
|
||||
description: 'Build progress tracking, session context, discoveries & gotchas recording',
|
||||
icon: ListChecks,
|
||||
tools: [
|
||||
|
||||
@@ -180,7 +180,7 @@ export function AppUpdateNotification() {
|
||||
<DialogDescription>
|
||||
{t(
|
||||
"dialogs:appUpdate.description",
|
||||
"A new version of Aperant is ready to download"
|
||||
"A new version of Auto Claude is ready to download"
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -277,7 +277,7 @@ export function AppUpdateNotification() {
|
||||
{t("dialogs:appUpdate.readOnlyVolumeTitle", "Cannot install from disk image")}
|
||||
</p>
|
||||
<p className="text-muted-foreground">
|
||||
{t("dialogs:appUpdate.readOnlyVolumeDescription", "Please move Aperant to your Applications folder before updating.")}
|
||||
{t("dialogs:appUpdate.readOnlyVolumeDescription", "Please move Auto Claude to your Applications folder before updating.")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -76,7 +76,7 @@ export function AuthFailureModal({ onOpenSettings }: AuthFailureModalProps) {
|
||||
{failureMessage}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('auth.failure.description', 'Please re-authenticate your Claude profile to continue using Aperant.')}
|
||||
{t('auth.failure.description', 'Please re-authenticate your Claude profile to continue using Auto Claude.')}
|
||||
</p>
|
||||
|
||||
{authFailureInfo.taskId && (
|
||||
|
||||
@@ -744,7 +744,7 @@ export function GitHubSetupModal({
|
||||
Select Base Branch
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Choose which branch Aperant should use as the base for creating task branches.
|
||||
Choose which branch Auto Claude should use as the base for creating task branches.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -811,7 +811,7 @@ export function GitHubSetupModal({
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<p className="font-medium text-foreground">Why select a branch?</p>
|
||||
<p className="mt-1">
|
||||
Aperant creates isolated workspaces for each task. Selecting the right base branch ensures
|
||||
Auto Claude creates isolated workspaces for each task. Selecting the right base branch ensures
|
||||
your tasks start with the latest code from your main development line.
|
||||
</p>
|
||||
</div>
|
||||
@@ -857,7 +857,7 @@ export function GitHubSetupModal({
|
||||
<CheckCircle2 className="h-8 w-8 text-success" />
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
Aperant is ready to use! You can now create tasks that will be
|
||||
Auto Claude is ready to use! You can now create tasks that will be
|
||||
automatically based on <code className="px-1 bg-muted rounded">{selectedBranch}</code>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -33,15 +33,11 @@ export function ProjectTabBar({
|
||||
// Keyboard shortcuts for tab navigation
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// Skip if in input fields (but NOT xterm's hidden textarea —
|
||||
// xterm already passes through Cmd/Ctrl+1-9 via attachCustomKeyEventHandler)
|
||||
const target = e.target as HTMLElement;
|
||||
const isXtermTextarea = target.classList?.contains('xterm-helper-textarea');
|
||||
// Skip if in input fields
|
||||
if (
|
||||
!isXtermTextarea &&
|
||||
(e.target instanceof HTMLInputElement ||
|
||||
e.target instanceof HTMLInputElement ||
|
||||
e.target instanceof HTMLTextAreaElement ||
|
||||
target?.isContentEditable)
|
||||
(e.target as HTMLElement)?.isContentEditable
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -480,7 +480,7 @@ export function Worktrees({ projectId }: WorktreesProps) {
|
||||
Worktrees
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Manage isolated workspaces for your Aperant tasks
|
||||
Manage isolated workspaces for your Auto Claude tasks
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -569,7 +569,7 @@ export function Worktrees({ projectId }: WorktreesProps) {
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-foreground">No Worktrees</h3>
|
||||
<p className="text-sm text-muted-foreground mt-2 max-w-md">
|
||||
Worktrees are created automatically when Aperant builds features.
|
||||
Worktrees are created automatically when Auto Claude builds features.
|
||||
You can also create terminal worktrees from the Agent Terminals tab.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -921,7 +921,7 @@ export function PRDetail({
|
||||
try {
|
||||
// Auto-assign current user (you can get from GitHub config)
|
||||
// For now, we'll just post the comment
|
||||
const approvalMessage = `## ✅ Aperant PR Review - APPROVED\n\n${reviewResult.summary}\n\n---\n*This approval was generated by Aperant.*`;
|
||||
const approvalMessage = `## ✅ Auto Claude PR Review - APPROVED\n\n${reviewResult.summary}\n\n---\n*This approval was generated by Auto Claude.*`;
|
||||
await Promise.resolve(onPostComment(approvalMessage));
|
||||
} finally {
|
||||
// Clear loading state if PR hasn't changed
|
||||
|
||||
@@ -395,7 +395,7 @@ describe('PRDetail Clean Review Functionality', () => {
|
||||
summary: 'All code passes review. No issues found.'
|
||||
});
|
||||
|
||||
const cleanReviewMessage = `## ✅ Aperant PR Review - PASSED
|
||||
const cleanReviewMessage = `## ✅ Auto Claude PR Review - PASSED
|
||||
|
||||
**Status:** All code is good
|
||||
|
||||
@@ -403,12 +403,12 @@ ${reviewResult.summary}
|
||||
|
||||
---
|
||||
|
||||
*This automated review found no issues. Generated by Aperant.*`;
|
||||
*This automated review found no issues. Generated by Auto Claude.*`;
|
||||
|
||||
expect(cleanReviewMessage).toContain('## ✅ Aperant PR Review - PASSED');
|
||||
expect(cleanReviewMessage).toContain('## ✅ Auto Claude PR Review - PASSED');
|
||||
expect(cleanReviewMessage).toContain('**Status:** All code is good');
|
||||
expect(cleanReviewMessage).toContain(reviewResult.summary);
|
||||
expect(cleanReviewMessage).toContain('*This automated review found no issues. Generated by Aperant.*');
|
||||
expect(cleanReviewMessage).toContain('*This automated review found no issues. Generated by Auto Claude.*');
|
||||
});
|
||||
|
||||
it('should include custom summary in clean review comment', () => {
|
||||
@@ -417,7 +417,7 @@ ${reviewResult.summary}
|
||||
summary: customSummary
|
||||
});
|
||||
|
||||
const cleanReviewMessage = `## ✅ Aperant PR Review - PASSED
|
||||
const cleanReviewMessage = `## ✅ Auto Claude PR Review - PASSED
|
||||
|
||||
**Status:** All code is good
|
||||
|
||||
@@ -425,7 +425,7 @@ ${reviewResult.summary}
|
||||
|
||||
---
|
||||
|
||||
*This automated review found no issues. Generated by Aperant.*`;
|
||||
*This automated review found no issues. Generated by Auto Claude.*`;
|
||||
|
||||
expect(cleanReviewMessage).toContain(customSummary);
|
||||
});
|
||||
@@ -435,7 +435,7 @@ ${reviewResult.summary}
|
||||
summary: ''
|
||||
});
|
||||
|
||||
const cleanReviewMessage = `## ✅ Aperant PR Review - PASSED
|
||||
const cleanReviewMessage = `## ✅ Auto Claude PR Review - PASSED
|
||||
|
||||
**Status:** All code is good
|
||||
|
||||
@@ -443,7 +443,7 @@ ${reviewResult.summary}
|
||||
|
||||
---
|
||||
|
||||
*This automated review found no issues. Generated by Aperant.*`;
|
||||
*This automated review found no issues. Generated by Auto Claude.*`;
|
||||
|
||||
expect(cleanReviewMessage).toBeDefined();
|
||||
expect(cleanReviewMessage).toContain('All code is good');
|
||||
|
||||
@@ -174,7 +174,7 @@ export function ClaudeCodeStep({ onNext, onBack, onSkip }: ClaudeCodeStepProps)
|
||||
{t('claudeCode.info.title', 'What is Claude Code?')}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('claudeCode.info.description', "Claude Code is Anthropic's official CLI that powers Aperant's AI features. It provides secure authentication and direct access to Claude models.")}
|
||||
{t('claudeCode.info.description', "Claude Code is Anthropic's official CLI that powers Auto Claude's AI features. It provides secure authentication and direct access to Claude models.")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -97,7 +97,7 @@ export function FirstSpecStep({ onNext, onBack, onSkip, onOpenTaskCreator }: Fir
|
||||
Create Your First Task
|
||||
</h1>
|
||||
<p className="mt-2 text-muted-foreground">
|
||||
Describe what you want to build and let Aperant handle the rest
|
||||
Describe what you want to build and let Auto Claude handle the rest
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -757,7 +757,7 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) {
|
||||
Graphiti configured successfully
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-success/80">
|
||||
Memory features are enabled. Aperant will maintain context
|
||||
Memory features are enabled. Auto Claude will maintain context
|
||||
across sessions for improved code understanding.
|
||||
</p>
|
||||
</div>
|
||||
@@ -823,7 +823,7 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) {
|
||||
What is Graphiti?
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Graphiti is an intelligent memory layer that helps Aperant remember
|
||||
Graphiti is an intelligent memory layer that helps Auto Claude remember
|
||||
context across sessions. It uses a knowledge graph to store discoveries,
|
||||
patterns, and insights about your codebase.
|
||||
</p>
|
||||
|
||||
@@ -21,11 +21,11 @@ vi.mock('react-i18next', () => ({
|
||||
// Return the key itself or provide specific translations
|
||||
// Keys are without namespace since component uses useTranslation('namespace')
|
||||
const translations: Record<string, string> = {
|
||||
'welcome.title': 'Welcome to Aperant',
|
||||
'welcome.title': 'Welcome to Auto Claude',
|
||||
'welcome.subtitle': 'AI-powered autonomous coding assistant',
|
||||
'welcome.getStarted': 'Get Started',
|
||||
'welcome.skip': 'Skip Setup',
|
||||
'wizard.helpText': 'Let us help you get started with Aperant',
|
||||
'wizard.helpText': 'Let us help you get started with Auto Claude',
|
||||
'welcome.features.aiPowered.title': 'AI-Powered',
|
||||
'welcome.features.aiPowered.description': 'Powered by Claude',
|
||||
'welcome.features.specDriven.title': 'Spec-Driven',
|
||||
@@ -107,7 +107,7 @@ describe('OnboardingWizard Integration Tests', () => {
|
||||
render(<OnboardingWizard {...defaultProps} />);
|
||||
|
||||
// Start at welcome step
|
||||
expect(screen.getByText(/Welcome to Aperant/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Welcome to Auto Claude/)).toBeInTheDocument();
|
||||
|
||||
// Click "Get Started" to go to accounts
|
||||
const getStartedButton = screen.getByRole('button', { name: /Get Started/ });
|
||||
@@ -147,7 +147,7 @@ describe('OnboardingWizard Integration Tests', () => {
|
||||
|
||||
// Should be back at welcome
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Welcome to Aperant/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Welcome to Auto Claude/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -157,25 +157,25 @@ describe('OnboardingWizard Integration Tests', () => {
|
||||
render(<OnboardingWizard {...defaultProps} open={true} />);
|
||||
|
||||
// Wizard should be visible
|
||||
expect(screen.getByText(/Welcome to Aperant/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Welcome to Auto Claude/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show wizard when open is false', () => {
|
||||
const { rerender } = render(<OnboardingWizard {...defaultProps} open={true} />);
|
||||
|
||||
expect(screen.getByText(/Welcome to Aperant/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Welcome to Auto Claude/)).toBeInTheDocument();
|
||||
|
||||
// Close wizard
|
||||
rerender(<OnboardingWizard {...defaultProps} open={false} />);
|
||||
|
||||
// Wizard content should not be visible
|
||||
expect(screen.queryByText(/Welcome to Aperant/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/Welcome to Auto Claude/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show wizard for users with existing auth', () => {
|
||||
render(<OnboardingWizard {...defaultProps} open={false} />);
|
||||
|
||||
expect(screen.queryByText(/Welcome to Aperant/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/Welcome to Auto Claude/)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -110,20 +110,6 @@ export function AddAccountDialog({
|
||||
}
|
||||
}, [open, editAccount, provider, billingModelOverride]);
|
||||
|
||||
// Parse DUPLICATE_EMAIL error from backend and show user-friendly toast
|
||||
const handleDuplicateEmailError = useCallback((error: string): boolean => {
|
||||
if (error.startsWith('DUPLICATE_EMAIL:')) {
|
||||
const existingName = error.slice('DUPLICATE_EMAIL:'.length);
|
||||
toast({
|
||||
variant: 'destructive',
|
||||
title: t('providers.dialog.toast.error'),
|
||||
description: t('providers.dialog.toast.duplicateEmail', { existingName }),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [toast, t]);
|
||||
|
||||
const isOAuthOnly = (provider === 'anthropic' || provider === 'openai') && authType === 'oauth';
|
||||
const isCodexOAuth = provider === 'openai' && authType === 'oauth';
|
||||
|
||||
@@ -204,16 +190,10 @@ export function AddAccountDialog({
|
||||
: t('providers.dialog.toast.added'),
|
||||
description: name.trim(),
|
||||
});
|
||||
} else if (result.error && !handleDuplicateEmailError(result.error)) {
|
||||
toast({
|
||||
variant: 'destructive',
|
||||
title: t('providers.dialog.toast.error'),
|
||||
description: result.error,
|
||||
});
|
||||
}
|
||||
};
|
||||
autoSave();
|
||||
}, [oauthStatus, isCodexOAuth, accountSaved, name, provider, oauthProfileId, isEditing, editAccount, oauthEmail, addProviderAccount, updateProviderAccount, handleDuplicateEmailError, toast, t, refreshUsageData]);
|
||||
}, [oauthStatus, isCodexOAuth, accountSaved, name, provider, oauthProfileId, isEditing, editAccount, oauthEmail, addProviderAccount, updateProviderAccount, toast, t, refreshUsageData]);
|
||||
|
||||
const canSave = () => {
|
||||
if (!name.trim()) return false;
|
||||
@@ -284,14 +264,8 @@ export function AddAccountDialog({
|
||||
description: name.trim(),
|
||||
});
|
||||
await refreshUsageData();
|
||||
onOpenChange(false);
|
||||
} else if (saveResult.error && !handleDuplicateEmailError(saveResult.error)) {
|
||||
toast({
|
||||
variant: 'destructive',
|
||||
title: t('providers.dialog.toast.error'),
|
||||
description: saveResult.error,
|
||||
});
|
||||
}
|
||||
onOpenChange(false);
|
||||
}, 800);
|
||||
} else {
|
||||
setOauthStatus('error');
|
||||
@@ -344,7 +318,7 @@ export function AddAccountDialog({
|
||||
setOauthStatus('error');
|
||||
setOauthError(err instanceof Error ? err.message : 'Unexpected error');
|
||||
}
|
||||
}, [name, t, toast, isCodexOAuth, isEditing, editAccount, provider, addProviderAccount, updateProviderAccount, handleDuplicateEmailError, onOpenChange, refreshUsageData]);
|
||||
}, [name, t, toast, isCodexOAuth, isEditing, editAccount, provider, addProviderAccount, updateProviderAccount, onOpenChange, refreshUsageData]);
|
||||
|
||||
const handleFallbackTerminal = useCallback(async () => {
|
||||
if (!name.trim()) {
|
||||
@@ -368,7 +342,7 @@ export function AddAccountDialog({
|
||||
createdAt: new Date(),
|
||||
});
|
||||
if (!profileResult.success || !profileResult.data) {
|
||||
toast({ variant: 'destructive', title: t('providers.dialog.toast.createProfileFailed') });
|
||||
toast({ variant: 'destructive', title: 'Failed to create profile' });
|
||||
return;
|
||||
}
|
||||
profileId = profileResult.data.id;
|
||||
@@ -378,7 +352,7 @@ export function AddAccountDialog({
|
||||
// Get terminal config for embedded AuthTerminal
|
||||
const authResult = await window.electronAPI.authenticateClaudeProfile(profileId);
|
||||
if (!authResult.success || !authResult.data) {
|
||||
toast({ variant: 'destructive', title: authResult.error ?? t('providers.dialog.toast.authPrepareFailed') });
|
||||
toast({ variant: 'destructive', title: authResult.error ?? 'Failed to prepare terminal' });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -388,7 +362,7 @@ export function AddAccountDialog({
|
||||
} catch (err) {
|
||||
toast({
|
||||
variant: 'destructive',
|
||||
title: err instanceof Error ? err.message : t('providers.dialog.toast.unexpectedError'),
|
||||
title: err instanceof Error ? err.message : 'Unexpected error',
|
||||
});
|
||||
}
|
||||
}, [name, oauthProfileId, t, toast]);
|
||||
@@ -447,7 +421,7 @@ export function AddAccountDialog({
|
||||
description: name.trim(),
|
||||
});
|
||||
onOpenChange(false);
|
||||
} else if (result.error && !handleDuplicateEmailError(result.error)) {
|
||||
} else {
|
||||
toast({
|
||||
variant: 'destructive',
|
||||
title: t('providers.dialog.toast.error'),
|
||||
|
||||
@@ -51,8 +51,9 @@ export function AuthTerminal({
|
||||
const loginSentRef = useRef(false); // Track if /login was already sent
|
||||
const loginTimeoutRef = useRef<NodeJS.Timeout | null>(null); // Track setTimeout for cleanup
|
||||
const successTimeoutRef = useRef<NodeJS.Timeout | null>(null); // Track success auto-close timeout for cleanup
|
||||
const authCompletedRef = useRef(false); // Track if auth has already completed to prevent race conditions
|
||||
|
||||
const [status, setStatus] = useState<'connecting' | 'ready' | 'success' | 'error'>('connecting');
|
||||
const [status, setStatus] = useState<'connecting' | 'ready' | 'onboarding' | 'success' | 'error'>('connecting');
|
||||
const [authEmail, setAuthEmail] = useState<string | undefined>();
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>();
|
||||
|
||||
@@ -234,6 +235,7 @@ export function AuthTerminal({
|
||||
thisTerminalId: terminalId,
|
||||
isMatch: info.terminalId === terminalId,
|
||||
success: info.success,
|
||||
needsOnboarding: info.needsOnboarding,
|
||||
email: info.email,
|
||||
currentStatus: statusRef.current,
|
||||
loginSent: loginSentRef.current
|
||||
@@ -241,9 +243,16 @@ export function AuthTerminal({
|
||||
if (info.terminalId === terminalId) {
|
||||
if (info.success) {
|
||||
setAuthEmail(info.email);
|
||||
debugLog('Setting status to success', { terminalId });
|
||||
setStatus('success');
|
||||
onAuthSuccess?.(info.email);
|
||||
// If needsOnboarding is true, user should complete setup in terminal
|
||||
// Otherwise, authentication is fully complete
|
||||
if (info.needsOnboarding) {
|
||||
debugLog('Setting status to onboarding', { terminalId });
|
||||
setStatus('onboarding');
|
||||
} else {
|
||||
debugLog('Setting status to success (no onboarding needed)', { terminalId });
|
||||
setStatus('success');
|
||||
onAuthSuccess?.(info.email);
|
||||
}
|
||||
} else {
|
||||
debugLog('OAuth failed', { terminalId, message: info.message });
|
||||
setStatus('error');
|
||||
@@ -263,13 +272,60 @@ export function AuthTerminal({
|
||||
terminalId,
|
||||
exitCode,
|
||||
currentStatus: statusRef.current,
|
||||
loginSent: loginSentRef.current
|
||||
loginSent: loginSentRef.current,
|
||||
willTransitionToSuccess: statusRef.current === 'onboarding' && exitCode === 0
|
||||
});
|
||||
// If we were in onboarding status and terminal exits with code 0,
|
||||
// that means the user completed the onboarding successfully
|
||||
if (statusRef.current === 'onboarding' && exitCode === 0) {
|
||||
// Prevent race condition with onboarding-complete handler
|
||||
if (authCompletedRef.current) {
|
||||
debugLog('SKIPPED exit handler - auth already completed', { terminalId });
|
||||
return;
|
||||
}
|
||||
authCompletedRef.current = true;
|
||||
debugLog('Transitioning from onboarding to success', { terminalId });
|
||||
setStatus('success');
|
||||
onAuthSuccess?.(authEmailRef.current);
|
||||
}
|
||||
// Don't close automatically - let user see any error messages
|
||||
}
|
||||
});
|
||||
cleanupFnsRef.current.push(unsubExit);
|
||||
|
||||
// Handle onboarding complete (Claude shows ready state after login)
|
||||
const unsubOnboardingComplete = window.electronAPI.onTerminalOnboardingComplete((info) => {
|
||||
if (info.terminalId === terminalId) {
|
||||
console.warn('[AuthTerminal] Onboarding complete:', info);
|
||||
debugLog('Onboarding complete event', {
|
||||
terminalId: info.terminalId,
|
||||
profileId: info.profileId,
|
||||
currentStatus: statusRef.current
|
||||
});
|
||||
// Only process if we're in onboarding status
|
||||
if (statusRef.current === 'onboarding') {
|
||||
// Prevent race condition with terminal exit handler
|
||||
if (authCompletedRef.current) {
|
||||
debugLog('SKIPPED onboarding-complete handler - auth already completed', { terminalId });
|
||||
return;
|
||||
}
|
||||
authCompletedRef.current = true;
|
||||
debugLog('Auto-closing terminal after onboarding complete', { terminalId });
|
||||
setStatus('success');
|
||||
onAuthSuccess?.(authEmailRef.current);
|
||||
// Auto-close after a brief delay to show success UI
|
||||
successTimeoutRef.current = setTimeout(() => {
|
||||
if (isCreatedRef.current) {
|
||||
window.electronAPI.destroyTerminal(terminalId).catch(console.error);
|
||||
isCreatedRef.current = false;
|
||||
}
|
||||
onClose();
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
});
|
||||
cleanupFnsRef.current.push(unsubOnboardingComplete);
|
||||
|
||||
return () => {
|
||||
debugLog('Cleaning up event listeners', { terminalId });
|
||||
inputDisposable.dispose();
|
||||
@@ -352,6 +408,9 @@ export function AuthTerminal({
|
||||
{status === 'ready' && (
|
||||
<div className="h-2 w-2 rounded-full bg-yellow-500 animate-pulse" />
|
||||
)}
|
||||
{status === 'onboarding' && (
|
||||
<div className="h-2 w-2 rounded-full bg-blue-500 animate-pulse" />
|
||||
)}
|
||||
{status === 'success' && (
|
||||
<CheckCircle2 className="h-4 w-4 text-success" />
|
||||
)}
|
||||
@@ -361,6 +420,7 @@ export function AuthTerminal({
|
||||
<span className="text-sm font-medium">
|
||||
{status === 'connecting' && t('authTerminal.connecting')}
|
||||
{status === 'ready' && t('authTerminal.authenticate', { profileName })}
|
||||
{status === 'onboarding' && t('authTerminal.completeSetup', { profileName })}
|
||||
{status === 'success' && (authEmail ? t('authTerminal.authenticatedAs', { email: authEmail }) : t('authTerminal.authenticated'))}
|
||||
{status === 'error' && t('authTerminal.authError')}
|
||||
</span>
|
||||
@@ -386,6 +446,13 @@ export function AuthTerminal({
|
||||
/>
|
||||
|
||||
{/* Status bar */}
|
||||
{status === 'onboarding' && (
|
||||
<div className="px-3 py-2 border-t border-border bg-blue-500/10">
|
||||
<p className="text-sm text-blue-600 dark:text-blue-400">
|
||||
{t('authTerminal.onboardingMessage')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{status === 'success' && (
|
||||
<div className="px-3 py-2 border-t border-border bg-success/10">
|
||||
<p className="text-sm text-success">
|
||||
|
||||
@@ -96,7 +96,7 @@ export function DebugSettings() {
|
||||
{t('debug.errorReporting.label', 'Anonymous Error Reporting')}
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
{t('debug.errorReporting.description', 'Send crash reports to help improve Aperant. No personal data or code is collected.')}
|
||||
{t('debug.errorReporting.description', 'Send crash reports to help improve Auto Claude. No personal data or code is collected.')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -320,7 +320,7 @@ export function DevToolsSettings({ settings, onSettingsChange }: DevToolsSetting
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('devtools.ide.description', 'Aperant will open worktrees in this editor')}
|
||||
{t('devtools.ide.description', 'Auto Claude will open worktrees in this editor')}
|
||||
</p>
|
||||
|
||||
{/* Custom IDE Path */}
|
||||
@@ -382,7 +382,7 @@ export function DevToolsSettings({ settings, onSettingsChange }: DevToolsSetting
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('devtools.terminal.description', 'Aperant will open terminal sessions here')}
|
||||
{t('devtools.terminal.description', 'Auto Claude will open terminal sessions here')}
|
||||
</p>
|
||||
|
||||
{/* Custom Terminal Path */}
|
||||
|
||||
@@ -104,6 +104,7 @@ export const terminalMock = {
|
||||
onTerminalAuthCreated: () => () => {},
|
||||
onTerminalClaudeBusy: () => () => {},
|
||||
onTerminalClaudeExit: () => () => {},
|
||||
onTerminalOnboardingComplete: () => () => {},
|
||||
onTerminalPendingResume: () => () => {},
|
||||
onTerminalProfileChanged: () => () => {},
|
||||
onTerminalOAuthCodeNeeded: () => () => {},
|
||||
|
||||
@@ -109,6 +109,7 @@ export const IPC_CHANNELS = {
|
||||
TERMINAL_OAUTH_CODE_SUBMIT: 'terminal:oauthCodeSubmit', // User submitted OAuth code to send to terminal
|
||||
TERMINAL_CLAUDE_BUSY: 'terminal:claudeBusy', // Claude Code busy state (for visual indicator)
|
||||
TERMINAL_CLAUDE_EXIT: 'terminal:claudeExit', // Claude Code exited (returned to shell)
|
||||
TERMINAL_ONBOARDING_COMPLETE: 'terminal:onboardingComplete', // Claude onboarding complete (ready for input after login)
|
||||
TERMINAL_PROFILE_CHANGED: 'terminal:profileChanged', // Profile changed, terminals need refresh (main -> renderer)
|
||||
|
||||
// Claude profile management (multi-account support)
|
||||
|
||||
@@ -71,12 +71,6 @@ export const ALL_AVAILABLE_MODELS: ModelOption[] = [
|
||||
{ value: 'glm-4.7', label: 'GLM-4.7', provider: 'zai', description: 'Previous flagship', capabilities: { thinking: false, tools: true, vision: false, contextWindow: 128000 } },
|
||||
{ value: 'glm-4.6v', label: 'GLM-4.6V', provider: 'zai', description: 'Multimodal', capabilities: { thinking: false, tools: true, vision: true, contextWindow: 128000 } },
|
||||
{ value: 'glm-4.5-flash', label: 'GLM-4.5 Flash', provider: 'zai', description: 'Fast', capabilities: { thinking: false, tools: true, vision: false, contextWindow: 128000 } },
|
||||
// Mascarade (LLM Orchestration Engine)
|
||||
{ value: 'mascarade-router', label: 'Mascarade Router', provider: 'mascarade', description: 'Multi-LLM routing (best/cheapest/fastest)', capabilities: { thinking: false, tools: true, vision: false, contextWindow: 128000 } },
|
||||
{ value: 'mascarade-writer', label: 'Mascarade Writer', provider: 'mascarade', description: 'Content generation agent', capabilities: { thinking: false, tools: true, vision: false, contextWindow: 128000 } },
|
||||
{ value: 'mascarade-coder', label: 'Mascarade Coder', provider: 'mascarade', description: 'Code review & generation', capabilities: { thinking: false, tools: true, vision: false, contextWindow: 128000 } },
|
||||
{ value: 'mascarade-analyst', label: 'Mascarade Analyst', provider: 'mascarade', description: 'Data analysis agent', capabilities: { thinking: false, tools: true, vision: false, contextWindow: 128000 } },
|
||||
{ value: 'mascarade-planner', label: 'Mascarade Planner', provider: 'mascarade', description: 'Project decomposition', capabilities: { thinking: false, tools: true, vision: false, contextWindow: 128000 } },
|
||||
];
|
||||
|
||||
// Maps model shorthand to actual Claude model IDs
|
||||
@@ -328,12 +322,6 @@ export const PROVIDER_PRESET_DEFINITIONS: Partial<Record<BuiltinProvider, Record
|
||||
balanced: { primaryModel: '', primaryThinking: 'low', phaseModels: { spec: '', planning: '', coding: '', qa: '' }, phaseThinking: { spec: 'low', planning: 'low', coding: 'low', qa: 'low' } },
|
||||
quick: { primaryModel: '', primaryThinking: 'low', phaseModels: { spec: '', planning: '', coding: '', qa: '' }, phaseThinking: { spec: 'low', planning: 'low', coding: 'low', qa: 'low' } },
|
||||
},
|
||||
mascarade: {
|
||||
auto: { primaryModel: 'mascarade-router', primaryThinking: 'low', phaseModels: { spec: 'mascarade-router', planning: 'mascarade-router', coding: 'mascarade-router', qa: 'mascarade-router' }, phaseThinking: { spec: 'low', planning: 'low', coding: 'low', qa: 'low' } },
|
||||
complex: { primaryModel: 'mascarade-router', primaryThinking: 'low', phaseModels: { spec: 'mascarade-router', planning: 'mascarade-router', coding: 'mascarade-router', qa: 'mascarade-router' }, phaseThinking: { spec: 'low', planning: 'low', coding: 'low', qa: 'low' } },
|
||||
balanced: { primaryModel: 'mascarade-router', primaryThinking: 'low', phaseModels: { spec: 'mascarade-router', planning: 'mascarade-router', coding: 'mascarade-router', qa: 'mascarade-router' }, phaseThinking: { spec: 'low', planning: 'low', coding: 'low', qa: 'low' } },
|
||||
quick: { primaryModel: 'mascarade-router', primaryThinking: 'low', phaseModels: { spec: 'mascarade-router', planning: 'mascarade-router', coding: 'mascarade-router', qa: 'mascarade-router' }, phaseThinking: { spec: 'low', planning: 'low', coding: 'low', qa: 'low' } },
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -73,11 +73,4 @@ export const PROVIDER_REGISTRY: ProviderInfo[] = [
|
||||
authMethods: ['api-key'], envVars: [],
|
||||
configFields: ['baseUrl'],
|
||||
},
|
||||
{
|
||||
id: 'mascarade', name: 'Mascarade', description: 'LLM Orchestration Engine — multi-provider routing with 19+ agents',
|
||||
category: 'local',
|
||||
authMethods: ['api-key'], envVars: ['MASCARADE_API_KEY'],
|
||||
configFields: ['baseUrl'],
|
||||
website: 'https://mascarade.saillant.cc',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -402,17 +402,17 @@
|
||||
"postCleanReview": "Post Clean Review",
|
||||
"postingCleanReview": "Posting...",
|
||||
"cleanReviewPosted": "Clean review posted",
|
||||
"cleanReviewMessageTitle": "## ✅ Aperant PR Review - PASSED",
|
||||
"cleanReviewMessageTitle": "## ✅ Auto Claude PR Review - PASSED",
|
||||
"cleanReviewMessageStatus": "**Status:** All code is good",
|
||||
"cleanReviewMessageFooter": "*This automated review found no issues. Generated by Aperant.*",
|
||||
"cleanReviewMessageFooter": "*This automated review found no issues. Generated by Auto Claude.*",
|
||||
"failedPostCleanReview": "Failed to post clean review",
|
||||
"viewErrorDetails": "View details",
|
||||
"hideErrorDetails": "Hide details",
|
||||
"postBlockedStatus": "Post Status",
|
||||
"postingBlockedStatus": "Posting...",
|
||||
"blockedStatusPosted": "Status posted to PR",
|
||||
"blockedStatusMessageTitle": "## 🤖 Aperant PR Review",
|
||||
"blockedStatusMessageFooter": "*This review identified blockers that must be resolved before merge. Generated by Aperant.*",
|
||||
"blockedStatusMessageTitle": "## 🤖 Auto Claude PR Review",
|
||||
"blockedStatusMessageFooter": "*This review identified blockers that must be resolved before merge. Generated by Auto Claude.*",
|
||||
"failedPostBlockedStatus": "Failed to post status",
|
||||
"branchSynced": "Branch synced ({{count}} commit from base)",
|
||||
"branchSynced_plural": "Branch synced ({{count}} commits from base)",
|
||||
@@ -606,7 +606,7 @@
|
||||
"codeSubmitFailed": "Failed to Submit Code",
|
||||
"codeSubmitFailedDescription": "Please try again or copy the code manually to the terminal.",
|
||||
"authenticateTitle": "Authenticate with Claude",
|
||||
"authenticateDescription": "Aperant requires Claude AI authentication for AI-powered features like Roadmap generation, Task automation, and Ideation.",
|
||||
"authenticateDescription": "Auto Claude requires Claude AI authentication for AI-powered features like Roadmap generation, Task automation, and Ideation.",
|
||||
"authenticateTerminalInfo": "This will open a terminal with Claude CLI where you can authenticate. Your credentials are stored securely and are valid for 1 year.",
|
||||
"completeAuthTitle": "Complete Authentication",
|
||||
"terminalOpened": "A terminal window has opened with Claude CLI.",
|
||||
@@ -621,7 +621,7 @@
|
||||
"successTitle": "Successfully Authenticated!",
|
||||
"connectedAs": "Connected as {{email}}",
|
||||
"credentialsSaved": "Your Claude credentials have been saved",
|
||||
"canUseFeatures": "You can now use all Aperant AI features",
|
||||
"canUseFeatures": "You can now use all Auto Claude AI features",
|
||||
"authFailed": "Authentication Failed",
|
||||
"skipForNow": "Skip for now",
|
||||
"manualTokenEntry": "Manual Token Entry",
|
||||
@@ -674,9 +674,11 @@
|
||||
"authFailed": "Authentication failed",
|
||||
"connecting": "Connecting...",
|
||||
"authenticate": "Authenticate: {{profileName}}",
|
||||
"completeSetup": "Complete setup: {{profileName}}",
|
||||
"authenticatedAs": "Authenticated as {{email}}",
|
||||
"authenticated": "Authenticated!",
|
||||
"authError": "Authentication Error",
|
||||
"onboardingMessage": "Token received! Complete the Claude Code setup below - this window will close automatically when done.",
|
||||
"successMessage": "Authentication successful! Closing..."
|
||||
},
|
||||
"profileCreated": {
|
||||
@@ -747,7 +749,7 @@
|
||||
"tokenInvalid": "Your authentication token is invalid.",
|
||||
"tokenMissing": "No authentication token found.",
|
||||
"authFailed": "Authentication failed.",
|
||||
"description": "Please re-authenticate your Claude profile to continue using Aperant.",
|
||||
"description": "Please re-authenticate your Claude profile to continue using Auto Claude.",
|
||||
"taskAffected": "Task affected",
|
||||
"technicalDetails": "Technical details",
|
||||
"goToSettings": "Go to Settings"
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"initialize": {
|
||||
"title": "Initialize Aperant",
|
||||
"description": "This project doesn't have Aperant initialized. Would you like to set it up now?",
|
||||
"title": "Initialize Auto Claude",
|
||||
"description": "This project doesn't have Auto Claude initialized. Would you like to set it up now?",
|
||||
"willDo": "This will:",
|
||||
"createFolder": "Create a .auto-claude folder in your project",
|
||||
"copyFramework": "Copy the Aperant framework files",
|
||||
"copyFramework": "Copy the Auto Claude framework files",
|
||||
"setupSpecs": "Set up the specs directory for your tasks",
|
||||
"sourcePathNotConfigured": "Source path not configured",
|
||||
"sourcePathNotConfiguredDescription": "Please set the Aperant source path in App Settings before initializing.",
|
||||
"sourcePathNotConfiguredDescription": "Please set the Auto Claude source path in App Settings before initializing.",
|
||||
"initFailed": "Initialization Failed",
|
||||
"initFailedDescription": "Failed to initialize Aperant. Please try again."
|
||||
"initFailedDescription": "Failed to initialize Auto Claude. Please try again."
|
||||
},
|
||||
"gitSetup": {
|
||||
"title": "Git Repository Required",
|
||||
"description": "Aperant uses git to safely build features in isolated workspaces",
|
||||
"description": "Auto Claude uses git to safely build features in isolated workspaces",
|
||||
"notGitRepo": "This folder is not a git repository",
|
||||
"noCommits": "Git repository has no commits",
|
||||
"needsInit": "Git needs to be initialized before Aperant can manage your code.",
|
||||
"needsCommit": "At least one commit is required for Aperant to create worktrees.",
|
||||
"needsInit": "Git needs to be initialized before Auto Claude can manage your code.",
|
||||
"needsCommit": "At least one commit is required for Auto Claude to create worktrees.",
|
||||
"willSetup": "We'll set up git for you:",
|
||||
"initRepo": "Initialize a new git repository",
|
||||
"createCommit": "Create an initial commit with your current files",
|
||||
@@ -25,25 +25,25 @@
|
||||
"settingUp": "Setting up Git",
|
||||
"initializingRepo": "Initializing git repository and creating initial commit...",
|
||||
"success": "Git Initialized",
|
||||
"readyToUse": "Your project is now ready to use with Aperant!"
|
||||
"readyToUse": "Your project is now ready to use with Auto Claude!"
|
||||
},
|
||||
"githubSetup": {
|
||||
"connectTitle": "Connect to GitHub",
|
||||
"connectDescription": "Aperant requires GitHub to manage your code branches and keep tasks up to date.",
|
||||
"connectDescription": "Auto Claude requires GitHub to manage your code branches and keep tasks up to date.",
|
||||
"claudeTitle": "Connect to Claude AI",
|
||||
"claudeDescription": "Aperant uses Claude AI for intelligent features like Roadmap generation, Task automation, and Ideation.",
|
||||
"claudeDescription": "Auto Claude uses Claude AI for intelligent features like Roadmap generation, Task automation, and Ideation.",
|
||||
"aiProviderTitle": "Connect to AI",
|
||||
"aiProviderDescription": "Add an AI provider account to power features like Roadmap generation, Task automation, and Ideation.",
|
||||
"aiProviderReady": "You have at least one AI provider configured. You can continue to the next step.",
|
||||
"skipForNow": "Skip for now",
|
||||
"continue": "Continue",
|
||||
"selectRepo": "Select Repository",
|
||||
"repoDescription": "Aperant will use this repository for managing task branches and keeping your code up to date.",
|
||||
"repoDescription": "Auto Claude will use this repository for managing task branches and keeping your code up to date.",
|
||||
"selectBranch": "Select Base Branch",
|
||||
"branchDescription": "Choose which branch Aperant should use as the base for creating task branches.",
|
||||
"branchDescription": "Choose which branch Auto Claude should use as the base for creating task branches.",
|
||||
"whyBranch": "Why select a branch?",
|
||||
"branchExplanation": "Aperant creates isolated workspaces for each task. Selecting the right base branch ensures your tasks start with the latest code from your main development line.",
|
||||
"ready": "Aperant is ready to use! You can now create tasks that will be automatically based on the {{branchName}} branch.",
|
||||
"branchExplanation": "Auto Claude creates isolated workspaces for each task. Selecting the right base branch ensures your tasks start with the latest code from your main development line.",
|
||||
"ready": "Auto Claude is ready to use! You can now create tasks that will be automatically based on the {{branchName}} branch.",
|
||||
"createRepoAriaLabel": "Create new repository on GitHub",
|
||||
"linkRepoAriaLabel": "Link to existing repository",
|
||||
"goBackAriaLabel": "Go back to repository selection",
|
||||
@@ -53,9 +53,9 @@
|
||||
},
|
||||
"worktrees": {
|
||||
"title": "Worktrees",
|
||||
"description": "Manage isolated workspaces for your Aperant tasks",
|
||||
"description": "Manage isolated workspaces for your Auto Claude tasks",
|
||||
"empty": "No Worktrees",
|
||||
"emptyDescription": "Worktrees are created automatically when Aperant builds features. They provide isolated workspaces for each task.",
|
||||
"emptyDescription": "Worktrees are created automatically when Auto Claude builds features. They provide isolated workspaces for each task.",
|
||||
"merge": "Merge Worktree",
|
||||
"mergeDescription": "Merge changes from this worktree into the base branch.",
|
||||
"delete": "Delete Worktree?",
|
||||
@@ -77,7 +77,7 @@
|
||||
"errorDescription": "Failed to cleanup worktree. Please try again."
|
||||
},
|
||||
"update": {
|
||||
"title": "Aperant",
|
||||
"title": "Auto Claude",
|
||||
"projectInitialized": "Project is initialized."
|
||||
},
|
||||
"addFeature": {
|
||||
@@ -156,7 +156,7 @@
|
||||
},
|
||||
"appUpdate": {
|
||||
"title": "App Update Available",
|
||||
"description": "A new version of Aperant is ready to download",
|
||||
"description": "A new version of Auto Claude is ready to download",
|
||||
"newVersion": "New Version",
|
||||
"released": "Released",
|
||||
"downloading": "Downloading...",
|
||||
@@ -169,7 +169,7 @@
|
||||
"claudeCodeChangelog": "View Claude Code Changelog",
|
||||
"claudeCodeChangelogAriaLabel": "View Claude Code Changelog (opens in new window)",
|
||||
"readOnlyVolumeTitle": "Cannot install from disk image",
|
||||
"readOnlyVolumeDescription": "Please move Aperant to your Applications folder before updating."
|
||||
"readOnlyVolumeDescription": "Please move Auto Claude to your Applications folder before updating."
|
||||
},
|
||||
"addCompetitor": {
|
||||
"title": "Add Competitor",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"help": "Help & Feedback"
|
||||
},
|
||||
"messages": {
|
||||
"initializeToCreateTasks": "Initialize Aperant to create tasks"
|
||||
"initializeToCreateTasks": "Initialize Auto Claude to create tasks"
|
||||
},
|
||||
"updateBanner": {
|
||||
"title": "Update Available",
|
||||
|
||||
@@ -810,11 +810,7 @@
|
||||
"toast": {
|
||||
"added": "Account added",
|
||||
"updated": "Account updated",
|
||||
"error": "Failed to save account",
|
||||
"duplicateEmail": "This email is already registered as \"{{existingName}}\"",
|
||||
"createProfileFailed": "Failed to create profile",
|
||||
"authPrepareFailed": "Failed to prepare terminal",
|
||||
"unexpectedError": "Unexpected error"
|
||||
"error": "Failed to save account"
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
@@ -922,7 +918,7 @@
|
||||
"description": "Web browser automation for testing"
|
||||
},
|
||||
"autoClaude": {
|
||||
"name": "Aperant Tools",
|
||||
"name": "Auto-Claude Tools",
|
||||
"description": "Build progress tracking"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"hero": {
|
||||
"title": "Welcome to Aperant",
|
||||
"title": "Welcome to Auto Claude",
|
||||
"subtitle": "Build software autonomously with AI-powered agents"
|
||||
},
|
||||
"actions": {
|
||||
|
||||
@@ -411,17 +411,17 @@
|
||||
"postCleanReview": "Publier révision propre",
|
||||
"postingCleanReview": "Publication...",
|
||||
"cleanReviewPosted": "Révision propre publiée",
|
||||
"cleanReviewMessageTitle": "## ✅ Aperant PR Review - PASSED",
|
||||
"cleanReviewMessageTitle": "## ✅ Auto Claude PR Review - PASSED",
|
||||
"cleanReviewMessageStatus": "**Status:** All code is good",
|
||||
"cleanReviewMessageFooter": "*This automated review found no issues. Generated by Aperant.*",
|
||||
"cleanReviewMessageFooter": "*This automated review found no issues. Generated by Auto Claude.*",
|
||||
"failedPostCleanReview": "Échec de la publication de la révision",
|
||||
"viewErrorDetails": "Voir les détails",
|
||||
"hideErrorDetails": "Masquer les détails",
|
||||
"postBlockedStatus": "Publier le statut",
|
||||
"postingBlockedStatus": "Publication...",
|
||||
"blockedStatusPosted": "Statut publié sur la PR",
|
||||
"blockedStatusMessageTitle": "## 🤖 Aperant PR Review",
|
||||
"blockedStatusMessageFooter": "*This review identified blockers that must be resolved before merge. Generated by Aperant.*",
|
||||
"blockedStatusMessageTitle": "## 🤖 Auto Claude PR Review",
|
||||
"blockedStatusMessageFooter": "*This review identified blockers that must be resolved before merge. Generated by Auto Claude.*",
|
||||
"failedPostBlockedStatus": "Échec de la publication du statut",
|
||||
"disputed": "Contesté",
|
||||
"disputedByValidator": "Contesté par le validateur ({{count}})",
|
||||
@@ -606,7 +606,7 @@
|
||||
"codeSubmitFailed": "Échec de la soumission du code",
|
||||
"codeSubmitFailedDescription": "Veuillez réessayer ou copier le code manuellement dans le terminal.",
|
||||
"authenticateTitle": "S'authentifier avec Claude",
|
||||
"authenticateDescription": "Aperant nécessite l'authentification Claude AI pour les fonctionnalités basées sur l'IA comme la génération de feuille de route, l'automatisation des tâches et l'idéation.",
|
||||
"authenticateDescription": "Auto Claude nécessite l'authentification Claude AI pour les fonctionnalités basées sur l'IA comme la génération de feuille de route, l'automatisation des tâches et l'idéation.",
|
||||
"authenticateTerminalInfo": "Cela ouvrira un terminal avec Claude CLI où vous pourrez vous authentifier. Vos identifiants sont stockés de manière sécurisée et sont valides pendant 1 an.",
|
||||
"completeAuthTitle": "Terminer l'authentification",
|
||||
"terminalOpened": "Une fenêtre de terminal s'est ouverte avec Claude CLI.",
|
||||
@@ -621,7 +621,7 @@
|
||||
"successTitle": "Authentification réussie !",
|
||||
"connectedAs": "Connecté en tant que {{email}}",
|
||||
"credentialsSaved": "Vos identifiants Claude ont été sauvegardés",
|
||||
"canUseFeatures": "Vous pouvez maintenant utiliser toutes les fonctionnalités IA d'Aperant",
|
||||
"canUseFeatures": "Vous pouvez maintenant utiliser toutes les fonctionnalités IA d'Auto Claude",
|
||||
"authFailed": "Échec de l'authentification",
|
||||
"skipForNow": "Passer pour l'instant",
|
||||
"manualTokenEntry": "Saisie manuelle du jeton",
|
||||
@@ -674,9 +674,11 @@
|
||||
"authFailed": "Échec de l'authentification",
|
||||
"connecting": "Connexion...",
|
||||
"authenticate": "Authentifier : {{profileName}}",
|
||||
"completeSetup": "Terminer la configuration : {{profileName}}",
|
||||
"authenticatedAs": "Authentifié en tant que {{email}}",
|
||||
"authenticated": "Authentifié !",
|
||||
"authError": "Erreur d'authentification",
|
||||
"onboardingMessage": "Jeton reçu ! Terminez la configuration de Claude Code ci-dessous - cette fenêtre se fermera automatiquement une fois terminé.",
|
||||
"successMessage": "Authentification réussie ! Fermeture..."
|
||||
},
|
||||
"profileCreated": {
|
||||
@@ -747,7 +749,7 @@
|
||||
"tokenInvalid": "Votre jeton d'authentification est invalide.",
|
||||
"tokenMissing": "Aucun jeton d'authentification trouvé.",
|
||||
"authFailed": "Échec de l'authentification.",
|
||||
"description": "Veuillez vous ré-authentifier pour continuer à utiliser Aperant.",
|
||||
"description": "Veuillez vous ré-authentifier pour continuer à utiliser Auto Claude.",
|
||||
"taskAffected": "Tâche affectée",
|
||||
"technicalDetails": "Détails techniques",
|
||||
"goToSettings": "Aller aux paramètres"
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"initialize": {
|
||||
"title": "Initialiser Aperant",
|
||||
"description": "Ce projet n'a pas Aperant initialisé. Voulez-vous le configurer maintenant ?",
|
||||
"title": "Initialiser Auto Claude",
|
||||
"description": "Ce projet n'a pas Auto Claude initialisé. Voulez-vous le configurer maintenant ?",
|
||||
"willDo": "Ceci va :",
|
||||
"createFolder": "Créer un dossier .auto-claude dans votre projet",
|
||||
"copyFramework": "Copier les fichiers du framework Aperant",
|
||||
"copyFramework": "Copier les fichiers du framework Auto Claude",
|
||||
"setupSpecs": "Configurer le répertoire des spécifications pour vos tâches",
|
||||
"sourcePathNotConfigured": "Chemin source non configuré",
|
||||
"sourcePathNotConfiguredDescription": "Veuillez définir le chemin source Aperant dans les paramètres de l'application avant d'initialiser.",
|
||||
"sourcePathNotConfiguredDescription": "Veuillez définir le chemin source Auto Claude dans les paramètres de l'application avant d'initialiser.",
|
||||
"initFailed": "Échec de l'initialisation",
|
||||
"initFailedDescription": "Échec de l'initialisation de Aperant. Veuillez réessayer."
|
||||
"initFailedDescription": "Échec de l'initialisation de Auto Claude. Veuillez réessayer."
|
||||
},
|
||||
"gitSetup": {
|
||||
"title": "Dépôt Git requis",
|
||||
"description": "Aperant utilise git pour construire des fonctionnalités en toute sécurité dans des espaces de travail isolés",
|
||||
"description": "Auto Claude utilise git pour construire des fonctionnalités en toute sécurité dans des espaces de travail isolés",
|
||||
"notGitRepo": "Ce dossier n'est pas un dépôt git",
|
||||
"noCommits": "Le dépôt git n'a pas de commits",
|
||||
"needsInit": "Git doit être initialisé avant que Aperant puisse gérer votre code.",
|
||||
"needsCommit": "Au moins un commit est requis pour que Aperant puisse créer des worktrees.",
|
||||
"needsInit": "Git doit être initialisé avant que Auto Claude puisse gérer votre code.",
|
||||
"needsCommit": "Au moins un commit est requis pour que Auto Claude puisse créer des worktrees.",
|
||||
"willSetup": "Nous allons configurer git pour vous :",
|
||||
"initRepo": "Initialiser un nouveau dépôt git",
|
||||
"createCommit": "Créer un commit initial avec vos fichiers actuels",
|
||||
@@ -25,25 +25,25 @@
|
||||
"settingUp": "Configuration de Git",
|
||||
"initializingRepo": "Initialisation du dépôt git et création du commit initial...",
|
||||
"success": "Git initialisé",
|
||||
"readyToUse": "Votre projet est maintenant prêt à être utilisé avec Aperant !"
|
||||
"readyToUse": "Votre projet est maintenant prêt à être utilisé avec Auto Claude !"
|
||||
},
|
||||
"githubSetup": {
|
||||
"connectTitle": "Connecter à GitHub",
|
||||
"connectDescription": "Aperant nécessite GitHub pour gérer vos branches de code et maintenir les tâches à jour.",
|
||||
"connectDescription": "Auto Claude nécessite GitHub pour gérer vos branches de code et maintenir les tâches à jour.",
|
||||
"claudeTitle": "Connecter à Claude AI",
|
||||
"claudeDescription": "Aperant utilise Claude AI pour des fonctionnalités intelligentes comme la génération de feuille de route, l'automatisation des tâches et l'idéation.",
|
||||
"claudeDescription": "Auto Claude utilise Claude AI pour des fonctionnalités intelligentes comme la génération de feuille de route, l'automatisation des tâches et l'idéation.",
|
||||
"aiProviderTitle": "Connecter à l'IA",
|
||||
"aiProviderDescription": "Ajoutez un compte fournisseur IA pour activer des fonctionnalités comme la génération de feuille de route, l'automatisation des tâches et l'idéation.",
|
||||
"aiProviderReady": "Vous avez au moins un fournisseur IA configuré. Vous pouvez passer à l'étape suivante.",
|
||||
"skipForNow": "Passer pour l'instant",
|
||||
"continue": "Continuer",
|
||||
"selectRepo": "Sélectionner le dépôt",
|
||||
"repoDescription": "Aperant utilisera ce dépôt pour gérer les branches de tâches et maintenir votre code à jour.",
|
||||
"repoDescription": "Auto Claude utilisera ce dépôt pour gérer les branches de tâches et maintenir votre code à jour.",
|
||||
"selectBranch": "Sélectionner la branche de base",
|
||||
"branchDescription": "Choisissez quelle branche Aperant doit utiliser comme base pour créer les branches de tâches.",
|
||||
"branchDescription": "Choisissez quelle branche Auto Claude doit utiliser comme base pour créer les branches de tâches.",
|
||||
"whyBranch": "Pourquoi sélectionner une branche ?",
|
||||
"branchExplanation": "Aperant crée des espaces de travail isolés pour chaque tâche. Sélectionner la bonne branche de base garantit que vos tâches démarrent avec le code le plus récent de votre ligne de développement principale.",
|
||||
"ready": "Aperant est prêt à l'emploi ! Vous pouvez maintenant créer des tâches qui seront automatiquement basées sur la branche {{branchName}}.",
|
||||
"branchExplanation": "Auto Claude crée des espaces de travail isolés pour chaque tâche. Sélectionner la bonne branche de base garantit que vos tâches démarrent avec le code le plus récent de votre ligne de développement principale.",
|
||||
"ready": "Auto Claude est prêt à l'emploi ! Vous pouvez maintenant créer des tâches qui seront automatiquement basées sur la branche {{branchName}}.",
|
||||
"createRepoAriaLabel": "Créer un nouveau dépôt sur GitHub",
|
||||
"linkRepoAriaLabel": "Lier à un dépôt existant",
|
||||
"goBackAriaLabel": "Retourner à la sélection du dépôt",
|
||||
@@ -53,9 +53,9 @@
|
||||
},
|
||||
"worktrees": {
|
||||
"title": "Worktrees",
|
||||
"description": "Gérez les espaces de travail isolés pour vos tâches Aperant",
|
||||
"description": "Gérez les espaces de travail isolés pour vos tâches Auto Claude",
|
||||
"empty": "Aucun worktree",
|
||||
"emptyDescription": "Les worktrees sont créés automatiquement quand Aperant construit des fonctionnalités. Ils fournissent des espaces de travail isolés pour chaque tâche.",
|
||||
"emptyDescription": "Les worktrees sont créés automatiquement quand Auto Claude construit des fonctionnalités. Ils fournissent des espaces de travail isolés pour chaque tâche.",
|
||||
"merge": "Fusionner le worktree",
|
||||
"mergeDescription": "Fusionner les modifications de ce worktree dans la branche de base.",
|
||||
"delete": "Supprimer le worktree ?",
|
||||
@@ -77,7 +77,7 @@
|
||||
"errorDescription": "Échec du nettoyage du worktree. Veuillez réessayer."
|
||||
},
|
||||
"update": {
|
||||
"title": "Aperant",
|
||||
"title": "Auto Claude",
|
||||
"projectInitialized": "Le projet est initialisé."
|
||||
},
|
||||
"addFeature": {
|
||||
@@ -156,7 +156,7 @@
|
||||
},
|
||||
"appUpdate": {
|
||||
"title": "Mise à jour de l'application disponible",
|
||||
"description": "Une nouvelle version d'Aperant est prête à être téléchargée",
|
||||
"description": "Une nouvelle version d'Auto Claude est prête à être téléchargée",
|
||||
"newVersion": "Nouvelle version",
|
||||
"released": "Publiée",
|
||||
"downloading": "Téléchargement...",
|
||||
@@ -169,7 +169,7 @@
|
||||
"claudeCodeChangelog": "Voir le journal des modifications Claude Code",
|
||||
"claudeCodeChangelogAriaLabel": "Voir le journal des modifications Claude Code (s'ouvre dans une nouvelle fenêtre)",
|
||||
"readOnlyVolumeTitle": "Impossible d'installer depuis une image disque",
|
||||
"readOnlyVolumeDescription": "Veuillez déplacer Aperant dans votre dossier Applications avant de mettre à jour."
|
||||
"readOnlyVolumeDescription": "Veuillez déplacer Auto Claude dans votre dossier Applications avant de mettre à jour."
|
||||
},
|
||||
"addCompetitor": {
|
||||
"title": "Ajouter un concurrent",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"help": "Aide & Feedback"
|
||||
},
|
||||
"messages": {
|
||||
"initializeToCreateTasks": "Initialisez Aperant pour créer des tâches"
|
||||
"initializeToCreateTasks": "Initialisez Auto Claude pour créer des tâches"
|
||||
},
|
||||
"updateBanner": {
|
||||
"title": "Mise à jour disponible",
|
||||
|
||||
@@ -810,11 +810,7 @@
|
||||
"toast": {
|
||||
"added": "Compte ajouté",
|
||||
"updated": "Compte mis à jour",
|
||||
"error": "Échec de l'enregistrement du compte",
|
||||
"duplicateEmail": "Cet e-mail est déjà enregistré sous \"{{existingName}}\"",
|
||||
"createProfileFailed": "Échec de la création du profil",
|
||||
"authPrepareFailed": "Échec de la préparation du terminal",
|
||||
"unexpectedError": "Erreur inattendue"
|
||||
"error": "Échec de l'enregistrement du compte"
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
@@ -922,7 +918,7 @@
|
||||
"description": "Automatisation du navigateur web pour les tests"
|
||||
},
|
||||
"autoClaude": {
|
||||
"name": "Outils Aperant",
|
||||
"name": "Outils Auto-Claude",
|
||||
"description": "Suivi de la progression du build"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"hero": {
|
||||
"title": "Bienvenue sur Aperant",
|
||||
"title": "Bienvenue sur Auto Claude",
|
||||
"subtitle": "Construisez des logiciels de manière autonome avec des agents IA"
|
||||
},
|
||||
"actions": {
|
||||
|
||||
@@ -287,6 +287,8 @@ export interface ElectronAPI {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
detectedAt: string;
|
||||
/** If true, user should complete onboarding in terminal before closing */
|
||||
needsOnboarding?: boolean;
|
||||
}) => void) => () => void;
|
||||
/** Listen for auth terminal creation - allows UI to display the OAuth terminal */
|
||||
onTerminalAuthCreated: (callback: (info: {
|
||||
@@ -298,6 +300,12 @@ export interface ElectronAPI {
|
||||
onTerminalClaudeBusy: (callback: (id: string, isBusy: boolean) => void) => () => void;
|
||||
/** Listen for Claude exit (user closed Claude within terminal, returned to shell) */
|
||||
onTerminalClaudeExit: (callback: (id: string) => void) => () => void;
|
||||
/** Listen for onboarding complete (Claude shows ready state after login/onboarding) */
|
||||
onTerminalOnboardingComplete: (callback: (info: {
|
||||
terminalId: string;
|
||||
profileId?: string;
|
||||
detectedAt: string;
|
||||
}) => void) => () => void;
|
||||
/** Listen for pending Claude resume notifications (for deferred resume on tab activation) */
|
||||
onTerminalPendingResume: (callback: (id: string, sessionId?: string) => void) => () => void;
|
||||
/** Listen for profile change events - terminals need to be recreated with new profile env vars */
|
||||
|
||||
@@ -7,7 +7,7 @@ export type CredentialSource = 'oauth' | 'api-key' | 'env' | 'keychain';
|
||||
export type BuiltinProvider =
|
||||
| 'anthropic' | 'openai' | 'google' | 'amazon-bedrock' | 'azure'
|
||||
| 'mistral' | 'groq' | 'xai' | 'openrouter' | 'zai'
|
||||
| 'ollama' | 'openai-compatible' | 'mascarade';
|
||||
| 'ollama' | 'openai-compatible';
|
||||
|
||||
export type BillingModel = 'subscription' | 'pay-per-use';
|
||||
|
||||
|
||||
@@ -45,6 +45,6 @@
|
||||
"lucide-react": "^0.562.0"
|
||||
},
|
||||
"overrides": {
|
||||
"@electron/rebuild": "4.0.3"
|
||||
"@electron/rebuild": "4.0.2"
|
||||
}
|
||||
}
|
||||
|
||||