mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
Merge branch 'main' of github.com:AutoMaker-Org/automaker into improve-context-page
This commit is contained in:
@@ -21,4 +21,4 @@
|
|||||||
"mcp__puppeteer__puppeteer_evaluate"
|
"mcp__puppeteer__puppeteer_evaluate"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
.github/actions/setup-project/action.yml
vendored
20
.github/actions/setup-project/action.yml
vendored
@@ -1,28 +1,28 @@
|
|||||||
name: "Setup Project"
|
name: 'Setup Project'
|
||||||
description: "Common setup steps for CI workflows - checkout, Node.js, dependencies, and native modules"
|
description: 'Common setup steps for CI workflows - checkout, Node.js, dependencies, and native modules'
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
node-version:
|
node-version:
|
||||||
description: "Node.js version to use"
|
description: 'Node.js version to use'
|
||||||
required: false
|
required: false
|
||||||
default: "22"
|
default: '22'
|
||||||
check-lockfile:
|
check-lockfile:
|
||||||
description: "Run lockfile lint check for SSH URLs"
|
description: 'Run lockfile lint check for SSH URLs'
|
||||||
required: false
|
required: false
|
||||||
default: "false"
|
default: 'false'
|
||||||
rebuild-node-pty-path:
|
rebuild-node-pty-path:
|
||||||
description: "Working directory for node-pty rebuild (empty = root)"
|
description: 'Working directory for node-pty rebuild (empty = root)'
|
||||||
required: false
|
required: false
|
||||||
default: ""
|
default: ''
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: 'composite'
|
||||||
steps:
|
steps:
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ inputs.node-version }}
|
node-version: ${{ inputs.node-version }}
|
||||||
cache: "npm"
|
cache: 'npm'
|
||||||
cache-dependency-path: package-lock.json
|
cache-dependency-path: package-lock.json
|
||||||
|
|
||||||
- name: Check for SSH URLs in lockfile
|
- name: Check for SSH URLs in lockfile
|
||||||
|
|||||||
179
.github/scripts/upload-to-r2.js
vendored
179
.github/scripts/upload-to-r2.js
vendored
@@ -1,15 +1,11 @@
|
|||||||
const {
|
const { S3Client, PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3');
|
||||||
S3Client,
|
const fs = require('fs');
|
||||||
PutObjectCommand,
|
const path = require('path');
|
||||||
GetObjectCommand,
|
const https = require('https');
|
||||||
} = require("@aws-sdk/client-s3");
|
const { pipeline } = require('stream/promises');
|
||||||
const fs = require("fs");
|
|
||||||
const path = require("path");
|
|
||||||
const https = require("https");
|
|
||||||
const { pipeline } = require("stream/promises");
|
|
||||||
|
|
||||||
const s3Client = new S3Client({
|
const s3Client = new S3Client({
|
||||||
region: "auto",
|
region: 'auto',
|
||||||
endpoint: process.env.R2_ENDPOINT,
|
endpoint: process.env.R2_ENDPOINT,
|
||||||
credentials: {
|
credentials: {
|
||||||
accessKeyId: process.env.R2_ACCESS_KEY_ID,
|
accessKeyId: process.env.R2_ACCESS_KEY_ID,
|
||||||
@@ -28,14 +24,14 @@ async function fetchExistingReleases() {
|
|||||||
const response = await s3Client.send(
|
const response = await s3Client.send(
|
||||||
new GetObjectCommand({
|
new GetObjectCommand({
|
||||||
Bucket: BUCKET,
|
Bucket: BUCKET,
|
||||||
Key: "releases.json",
|
Key: 'releases.json',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const body = await response.Body.transformToString();
|
const body = await response.Body.transformToString();
|
||||||
return JSON.parse(body);
|
return JSON.parse(body);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.name === "NoSuchKey" || error.$metadata?.httpStatusCode === 404) {
|
if (error.name === 'NoSuchKey' || error.$metadata?.httpStatusCode === 404) {
|
||||||
console.log("No existing releases.json found, creating new one");
|
console.log('No existing releases.json found, creating new one');
|
||||||
return { latestVersion: null, releases: [] };
|
return { latestVersion: null, releases: [] };
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
@@ -85,7 +81,7 @@ async function checkUrlAccessible(url, maxRetries = 10, initialDelay = 1000) {
|
|||||||
resolve({
|
resolve({
|
||||||
accessible: false,
|
accessible: false,
|
||||||
statusCode,
|
statusCode,
|
||||||
error: "Redirect without location header",
|
error: 'Redirect without location header',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -93,18 +89,16 @@ async function checkUrlAccessible(url, maxRetries = 10, initialDelay = 1000) {
|
|||||||
return https
|
return https
|
||||||
.get(redirectUrl, { timeout: 10000 }, (redirectResponse) => {
|
.get(redirectUrl, { timeout: 10000 }, (redirectResponse) => {
|
||||||
const redirectStatus = redirectResponse.statusCode;
|
const redirectStatus = redirectResponse.statusCode;
|
||||||
const contentType =
|
const contentType = redirectResponse.headers['content-type'] || '';
|
||||||
redirectResponse.headers["content-type"] || "";
|
|
||||||
// Check if it's actually a file (zip/tar.gz) and not HTML
|
// Check if it's actually a file (zip/tar.gz) and not HTML
|
||||||
const isFile =
|
const isFile =
|
||||||
contentType.includes("application/zip") ||
|
contentType.includes('application/zip') ||
|
||||||
contentType.includes("application/gzip") ||
|
contentType.includes('application/gzip') ||
|
||||||
contentType.includes("application/x-gzip") ||
|
contentType.includes('application/x-gzip') ||
|
||||||
contentType.includes("application/x-tar") ||
|
contentType.includes('application/x-tar') ||
|
||||||
redirectUrl.includes(".zip") ||
|
redirectUrl.includes('.zip') ||
|
||||||
redirectUrl.includes(".tar.gz");
|
redirectUrl.includes('.tar.gz');
|
||||||
const isGood =
|
const isGood = redirectStatus >= 200 && redirectStatus < 300 && isFile;
|
||||||
redirectStatus >= 200 && redirectStatus < 300 && isFile;
|
|
||||||
redirectResponse.destroy();
|
redirectResponse.destroy();
|
||||||
resolve({
|
resolve({
|
||||||
accessible: isGood,
|
accessible: isGood,
|
||||||
@@ -113,38 +107,38 @@ async function checkUrlAccessible(url, maxRetries = 10, initialDelay = 1000) {
|
|||||||
contentType,
|
contentType,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.on("error", (error) => {
|
.on('error', (error) => {
|
||||||
resolve({
|
resolve({
|
||||||
accessible: false,
|
accessible: false,
|
||||||
statusCode,
|
statusCode,
|
||||||
error: error.message,
|
error: error.message,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.on("timeout", function () {
|
.on('timeout', function () {
|
||||||
this.destroy();
|
this.destroy();
|
||||||
resolve({
|
resolve({
|
||||||
accessible: false,
|
accessible: false,
|
||||||
statusCode,
|
statusCode,
|
||||||
error: "Timeout following redirect",
|
error: 'Timeout following redirect',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if status is good (200-299 range) and it's actually a file
|
// Check if status is good (200-299 range) and it's actually a file
|
||||||
const contentType = response.headers["content-type"] || "";
|
const contentType = response.headers['content-type'] || '';
|
||||||
const isFile =
|
const isFile =
|
||||||
contentType.includes("application/zip") ||
|
contentType.includes('application/zip') ||
|
||||||
contentType.includes("application/gzip") ||
|
contentType.includes('application/gzip') ||
|
||||||
contentType.includes("application/x-gzip") ||
|
contentType.includes('application/x-gzip') ||
|
||||||
contentType.includes("application/x-tar") ||
|
contentType.includes('application/x-tar') ||
|
||||||
url.includes(".zip") ||
|
url.includes('.zip') ||
|
||||||
url.includes(".tar.gz");
|
url.includes('.tar.gz');
|
||||||
const isGood = statusCode >= 200 && statusCode < 300 && isFile;
|
const isGood = statusCode >= 200 && statusCode < 300 && isFile;
|
||||||
response.destroy();
|
response.destroy();
|
||||||
resolve({ accessible: isGood, statusCode, contentType });
|
resolve({ accessible: isGood, statusCode, contentType });
|
||||||
});
|
});
|
||||||
|
|
||||||
request.on("error", (error) => {
|
request.on('error', (error) => {
|
||||||
resolve({
|
resolve({
|
||||||
accessible: false,
|
accessible: false,
|
||||||
statusCode: null,
|
statusCode: null,
|
||||||
@@ -152,12 +146,12 @@ async function checkUrlAccessible(url, maxRetries = 10, initialDelay = 1000) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
request.on("timeout", () => {
|
request.on('timeout', () => {
|
||||||
request.destroy();
|
request.destroy();
|
||||||
resolve({
|
resolve({
|
||||||
accessible: false,
|
accessible: false,
|
||||||
statusCode: null,
|
statusCode: null,
|
||||||
error: "Request timeout",
|
error: 'Request timeout',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -168,22 +162,14 @@ async function checkUrlAccessible(url, maxRetries = 10, initialDelay = 1000) {
|
|||||||
`✓ URL ${url} is now accessible after ${attempt} retries (status: ${result.statusCode})`
|
`✓ URL ${url} is now accessible after ${attempt} retries (status: ${result.statusCode})`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.log(`✓ URL ${url} is accessible (status: ${result.statusCode})`);
|
||||||
`✓ URL ${url} is accessible (status: ${result.statusCode})`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return result.finalUrl || url; // Return the final URL (after redirects) if available
|
return result.finalUrl || url; // Return the final URL (after redirects) if available
|
||||||
} else {
|
} else {
|
||||||
const errorMsg = result.error ? ` - ${result.error}` : "";
|
const errorMsg = result.error ? ` - ${result.error}` : '';
|
||||||
const statusMsg = result.statusCode
|
const statusMsg = result.statusCode ? ` (status: ${result.statusCode})` : '';
|
||||||
? ` (status: ${result.statusCode})`
|
const contentTypeMsg = result.contentType ? ` [content-type: ${result.contentType}]` : '';
|
||||||
: "";
|
console.log(`✗ URL ${url} not accessible${statusMsg}${contentTypeMsg}${errorMsg}`);
|
||||||
const contentTypeMsg = result.contentType
|
|
||||||
? ` [content-type: ${result.contentType}]`
|
|
||||||
: "";
|
|
||||||
console.log(
|
|
||||||
`✗ URL ${url} not accessible${statusMsg}${contentTypeMsg}${errorMsg}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`✗ URL ${url} check failed: ${error.message}`);
|
console.log(`✗ URL ${url} check failed: ${error.message}`);
|
||||||
@@ -191,9 +177,7 @@ async function checkUrlAccessible(url, maxRetries = 10, initialDelay = 1000) {
|
|||||||
|
|
||||||
if (attempt < maxRetries - 1) {
|
if (attempt < maxRetries - 1) {
|
||||||
const delay = initialDelay * Math.pow(2, attempt);
|
const delay = initialDelay * Math.pow(2, attempt);
|
||||||
console.log(
|
console.log(` Retrying in ${delay}ms... (attempt ${attempt + 1}/${maxRetries})`);
|
||||||
` Retrying in ${delay}ms... (attempt ${attempt + 1}/${maxRetries})`
|
|
||||||
);
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,12 +191,7 @@ async function downloadFromGitHub(url, outputPath) {
|
|||||||
const statusCode = response.statusCode;
|
const statusCode = response.statusCode;
|
||||||
|
|
||||||
// Follow redirects (all redirect types)
|
// Follow redirects (all redirect types)
|
||||||
if (
|
if (statusCode === 301 || statusCode === 302 || statusCode === 307 || statusCode === 308) {
|
||||||
statusCode === 301 ||
|
|
||||||
statusCode === 302 ||
|
|
||||||
statusCode === 307 ||
|
|
||||||
statusCode === 308
|
|
||||||
) {
|
|
||||||
const redirectUrl = response.headers.location;
|
const redirectUrl = response.headers.location;
|
||||||
response.destroy();
|
response.destroy();
|
||||||
if (!redirectUrl) {
|
if (!redirectUrl) {
|
||||||
@@ -220,39 +199,33 @@ async function downloadFromGitHub(url, outputPath) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Resolve relative redirects
|
// Resolve relative redirects
|
||||||
const finalRedirectUrl = redirectUrl.startsWith("http")
|
const finalRedirectUrl = redirectUrl.startsWith('http')
|
||||||
? redirectUrl
|
? redirectUrl
|
||||||
: new URL(redirectUrl, url).href;
|
: new URL(redirectUrl, url).href;
|
||||||
console.log(` Following redirect: ${finalRedirectUrl}`);
|
console.log(` Following redirect: ${finalRedirectUrl}`);
|
||||||
return downloadFromGitHub(finalRedirectUrl, outputPath)
|
return downloadFromGitHub(finalRedirectUrl, outputPath).then(resolve).catch(reject);
|
||||||
.then(resolve)
|
|
||||||
.catch(reject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statusCode !== 200) {
|
if (statusCode !== 200) {
|
||||||
response.destroy();
|
response.destroy();
|
||||||
reject(
|
reject(new Error(`Failed to download ${url}: ${statusCode} ${response.statusMessage}`));
|
||||||
new Error(
|
|
||||||
`Failed to download ${url}: ${statusCode} ${response.statusMessage}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileStream = fs.createWriteStream(outputPath);
|
const fileStream = fs.createWriteStream(outputPath);
|
||||||
response.pipe(fileStream);
|
response.pipe(fileStream);
|
||||||
fileStream.on("finish", () => {
|
fileStream.on('finish', () => {
|
||||||
fileStream.close();
|
fileStream.close();
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
fileStream.on("error", (error) => {
|
fileStream.on('error', (error) => {
|
||||||
response.destroy();
|
response.destroy();
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
request.on("error", reject);
|
request.on('error', reject);
|
||||||
request.on("timeout", () => {
|
request.on('timeout', () => {
|
||||||
request.destroy();
|
request.destroy();
|
||||||
reject(new Error(`Request timeout for ${url}`));
|
reject(new Error(`Request timeout for ${url}`));
|
||||||
});
|
});
|
||||||
@@ -260,8 +233,8 @@ async function downloadFromGitHub(url, outputPath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const artifactsDir = "artifacts";
|
const artifactsDir = 'artifacts';
|
||||||
const tempDir = path.join(artifactsDir, "temp");
|
const tempDir = path.join(artifactsDir, 'temp');
|
||||||
|
|
||||||
// Create temp directory for downloaded GitHub archives
|
// Create temp directory for downloaded GitHub archives
|
||||||
if (!fs.existsSync(tempDir)) {
|
if (!fs.existsSync(tempDir)) {
|
||||||
@@ -292,40 +265,30 @@ async function main() {
|
|||||||
|
|
||||||
// Find all artifacts
|
// Find all artifacts
|
||||||
const artifacts = {
|
const artifacts = {
|
||||||
windows: findArtifacts(path.join(artifactsDir, "windows-builds"), /\.exe$/),
|
windows: findArtifacts(path.join(artifactsDir, 'windows-builds'), /\.exe$/),
|
||||||
macos: findArtifacts(path.join(artifactsDir, "macos-builds"), /-x64\.dmg$/),
|
macos: findArtifacts(path.join(artifactsDir, 'macos-builds'), /-x64\.dmg$/),
|
||||||
macosArm: findArtifacts(
|
macosArm: findArtifacts(path.join(artifactsDir, 'macos-builds'), /-arm64\.dmg$/),
|
||||||
path.join(artifactsDir, "macos-builds"),
|
linux: findArtifacts(path.join(artifactsDir, 'linux-builds'), /\.AppImage$/),
|
||||||
/-arm64\.dmg$/
|
|
||||||
),
|
|
||||||
linux: findArtifacts(
|
|
||||||
path.join(artifactsDir, "linux-builds"),
|
|
||||||
/\.AppImage$/
|
|
||||||
),
|
|
||||||
sourceZip: [sourceZipPath],
|
sourceZip: [sourceZipPath],
|
||||||
sourceTarGz: [sourceTarGzPath],
|
sourceTarGz: [sourceTarGzPath],
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("Found artifacts:");
|
console.log('Found artifacts:');
|
||||||
for (const [platform, files] of Object.entries(artifacts)) {
|
for (const [platform, files] of Object.entries(artifacts)) {
|
||||||
console.log(
|
console.log(
|
||||||
` ${platform}: ${
|
` ${platform}: ${files.length > 0 ? files.map((f) => path.basename(f)).join(', ') : 'none'}`
|
||||||
files.length > 0
|
|
||||||
? files.map((f) => path.basename(f)).join(", ")
|
|
||||||
: "none"
|
|
||||||
}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload each artifact to R2
|
// Upload each artifact to R2
|
||||||
const assets = {};
|
const assets = {};
|
||||||
const contentTypes = {
|
const contentTypes = {
|
||||||
windows: "application/x-msdownload",
|
windows: 'application/x-msdownload',
|
||||||
macos: "application/x-apple-diskimage",
|
macos: 'application/x-apple-diskimage',
|
||||||
macosArm: "application/x-apple-diskimage",
|
macosArm: 'application/x-apple-diskimage',
|
||||||
linux: "application/x-executable",
|
linux: 'application/x-executable',
|
||||||
sourceZip: "application/zip",
|
sourceZip: 'application/zip',
|
||||||
sourceTarGz: "application/gzip",
|
sourceTarGz: 'application/gzip',
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [platform, files] of Object.entries(artifacts)) {
|
for (const [platform, files] of Object.entries(artifacts)) {
|
||||||
@@ -345,11 +308,11 @@ async function main() {
|
|||||||
filename,
|
filename,
|
||||||
size,
|
size,
|
||||||
arch:
|
arch:
|
||||||
platform === "macosArm"
|
platform === 'macosArm'
|
||||||
? "arm64"
|
? 'arm64'
|
||||||
: platform === "sourceZip" || platform === "sourceTarGz"
|
: platform === 'sourceZip' || platform === 'sourceTarGz'
|
||||||
? "source"
|
? 'source'
|
||||||
: "x64",
|
: 'x64',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,9 +327,7 @@ async function main() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Remove existing entry for this version if re-running
|
// Remove existing entry for this version if re-running
|
||||||
releasesData.releases = releasesData.releases.filter(
|
releasesData.releases = releasesData.releases.filter((r) => r.version !== VERSION);
|
||||||
(r) => r.version !== VERSION
|
|
||||||
);
|
|
||||||
|
|
||||||
// Prepend new release
|
// Prepend new release
|
||||||
releasesData.releases.unshift(newRelease);
|
releasesData.releases.unshift(newRelease);
|
||||||
@@ -376,19 +337,19 @@ async function main() {
|
|||||||
await s3Client.send(
|
await s3Client.send(
|
||||||
new PutObjectCommand({
|
new PutObjectCommand({
|
||||||
Bucket: BUCKET,
|
Bucket: BUCKET,
|
||||||
Key: "releases.json",
|
Key: 'releases.json',
|
||||||
Body: JSON.stringify(releasesData, null, 2),
|
Body: JSON.stringify(releasesData, null, 2),
|
||||||
ContentType: "application/json",
|
ContentType: 'application/json',
|
||||||
CacheControl: "public, max-age=60",
|
CacheControl: 'public, max-age=60',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("Successfully updated releases.json");
|
console.log('Successfully updated releases.json');
|
||||||
console.log(`Latest version: ${VERSION}`);
|
console.log(`Latest version: ${VERSION}`);
|
||||||
console.log(`Total releases: ${releasesData.releases.length}`);
|
console.log(`Total releases: ${releasesData.releases.length}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((err) => {
|
main().catch((err) => {
|
||||||
console.error("Failed to upload to R2:", err);
|
console.error('Failed to upload to R2:', err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|||||||
1
.github/workflows/claude.yml
vendored
1
.github/workflows/claude.yml
vendored
@@ -47,4 +47,3 @@ jobs:
|
|||||||
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||||
# or https://code.claude.com/docs/en/cli-reference for available options
|
# or https://code.claude.com/docs/en/cli-reference for available options
|
||||||
# claude_args: '--allowed-tools Bash(gh pr:*)'
|
# claude_args: '--allowed-tools Bash(gh pr:*)'
|
||||||
|
|
||||||
|
|||||||
8
.github/workflows/e2e-tests.yml
vendored
8
.github/workflows/e2e-tests.yml
vendored
@@ -3,7 +3,7 @@ name: E2E Tests
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- "*"
|
- '*'
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
@@ -21,8 +21,8 @@ jobs:
|
|||||||
- name: Setup project
|
- name: Setup project
|
||||||
uses: ./.github/actions/setup-project
|
uses: ./.github/actions/setup-project
|
||||||
with:
|
with:
|
||||||
check-lockfile: "true"
|
check-lockfile: 'true'
|
||||||
rebuild-node-pty-path: "apps/server"
|
rebuild-node-pty-path: 'apps/server'
|
||||||
|
|
||||||
- name: Install Playwright browsers
|
- name: Install Playwright browsers
|
||||||
run: npx playwright install --with-deps chromium
|
run: npx playwright install --with-deps chromium
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
VITE_SERVER_URL: http://localhost:3008
|
VITE_SERVER_URL: http://localhost:3008
|
||||||
VITE_SKIP_SETUP: "true"
|
VITE_SKIP_SETUP: 'true'
|
||||||
|
|
||||||
- name: Upload Playwright report
|
- name: Upload Playwright report
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
4
.github/workflows/pr-check.yml
vendored
4
.github/workflows/pr-check.yml
vendored
@@ -3,7 +3,7 @@ name: PR Build Check
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- "*"
|
- '*'
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: Setup project
|
- name: Setup project
|
||||||
uses: ./.github/actions/setup-project
|
uses: ./.github/actions/setup-project
|
||||||
with:
|
with:
|
||||||
check-lockfile: "true"
|
check-lockfile: 'true'
|
||||||
|
|
||||||
- name: Run build:electron (dir only - faster CI)
|
- name: Run build:electron (dir only - faster CI)
|
||||||
run: npm run build:electron:dir
|
run: npm run build:electron:dir
|
||||||
|
|||||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -3,7 +3,7 @@ name: Test Suite
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- "*"
|
- '*'
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
@@ -20,8 +20,8 @@ jobs:
|
|||||||
- name: Setup project
|
- name: Setup project
|
||||||
uses: ./.github/actions/setup-project
|
uses: ./.github/actions/setup-project
|
||||||
with:
|
with:
|
||||||
check-lockfile: "true"
|
check-lockfile: 'true'
|
||||||
rebuild-node-pty-path: "apps/server"
|
rebuild-node-pty-path: 'apps/server'
|
||||||
|
|
||||||
- name: Run package tests
|
- name: Run package tests
|
||||||
run: npm run test:packages
|
run: npm run test:packages
|
||||||
|
|||||||
2
apps/app/next-env.d.ts
vendored
2
apps/app/next-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
import "./.next/dev/types/routes.d.ts";
|
import './.next/dev/types/routes.d.ts';
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
@@ -299,11 +299,34 @@ terminalWss.on('connection', (ws: WebSocket, req: import('http').IncomingMessage
|
|||||||
|
|
||||||
switch (msg.type) {
|
switch (msg.type) {
|
||||||
case 'input':
|
case 'input':
|
||||||
|
// Validate input data type and length
|
||||||
|
if (typeof msg.data !== 'string') {
|
||||||
|
ws.send(JSON.stringify({ type: 'error', message: 'Invalid input type' }));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Limit input size to 1MB to prevent memory issues
|
||||||
|
if (msg.data.length > 1024 * 1024) {
|
||||||
|
ws.send(JSON.stringify({ type: 'error', message: 'Input too large' }));
|
||||||
|
break;
|
||||||
|
}
|
||||||
// Write user input to terminal
|
// Write user input to terminal
|
||||||
terminalService.write(sessionId, msg.data);
|
terminalService.write(sessionId, msg.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'resize':
|
case 'resize':
|
||||||
|
// Validate resize dimensions are positive integers within reasonable bounds
|
||||||
|
if (
|
||||||
|
typeof msg.cols !== 'number' ||
|
||||||
|
typeof msg.rows !== 'number' ||
|
||||||
|
!Number.isInteger(msg.cols) ||
|
||||||
|
!Number.isInteger(msg.rows) ||
|
||||||
|
msg.cols < 1 ||
|
||||||
|
msg.cols > 1000 ||
|
||||||
|
msg.rows < 1 ||
|
||||||
|
msg.rows > 500
|
||||||
|
) {
|
||||||
|
break; // Silently ignore invalid resize requests
|
||||||
|
}
|
||||||
// Resize terminal with deduplication and rate limiting
|
// Resize terminal with deduplication and rate limiting
|
||||||
if (msg.cols && msg.rows) {
|
if (msg.cols && msg.rows) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|||||||
@@ -6,26 +6,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Import and re-export spec types from shared package
|
// Import and re-export spec types from shared package
|
||||||
export type { SpecOutput } from "@automaker/types";
|
export type { SpecOutput } from '@automaker/types';
|
||||||
export { specOutputSchema } from "@automaker/types";
|
export { specOutputSchema } from '@automaker/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Escape special XML characters
|
* Escape special XML characters
|
||||||
*/
|
*/
|
||||||
function escapeXml(str: string): string {
|
function escapeXml(str: string): string {
|
||||||
return str
|
return str
|
||||||
.replace(/&/g, "&")
|
.replace(/&/g, '&')
|
||||||
.replace(/</g, "<")
|
.replace(/</g, '<')
|
||||||
.replace(/>/g, ">")
|
.replace(/>/g, '>')
|
||||||
.replace(/"/g, """)
|
.replace(/"/g, '"')
|
||||||
.replace(/'/g, "'");
|
.replace(/'/g, ''');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert structured spec output to XML format
|
* Convert structured spec output to XML format
|
||||||
*/
|
*/
|
||||||
export function specToXml(spec: import("@automaker/types").SpecOutput): string {
|
export function specToXml(spec: import('@automaker/types').SpecOutput): string {
|
||||||
const indent = " ";
|
const indent = ' ';
|
||||||
|
|
||||||
let xml = `<?xml version="1.0" encoding="UTF-8"?>
|
let xml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project_specification>
|
<project_specification>
|
||||||
@@ -36,11 +36,11 @@ ${indent}${indent}${escapeXml(spec.overview)}
|
|||||||
${indent}</overview>
|
${indent}</overview>
|
||||||
|
|
||||||
${indent}<technology_stack>
|
${indent}<technology_stack>
|
||||||
${spec.technology_stack.map((t) => `${indent}${indent}<technology>${escapeXml(t)}</technology>`).join("\n")}
|
${spec.technology_stack.map((t) => `${indent}${indent}<technology>${escapeXml(t)}</technology>`).join('\n')}
|
||||||
${indent}</technology_stack>
|
${indent}</technology_stack>
|
||||||
|
|
||||||
${indent}<core_capabilities>
|
${indent}<core_capabilities>
|
||||||
${spec.core_capabilities.map((c) => `${indent}${indent}<capability>${escapeXml(c)}</capability>`).join("\n")}
|
${spec.core_capabilities.map((c) => `${indent}${indent}<capability>${escapeXml(c)}</capability>`).join('\n')}
|
||||||
${indent}</core_capabilities>
|
${indent}</core_capabilities>
|
||||||
|
|
||||||
${indent}<implemented_features>
|
${indent}<implemented_features>
|
||||||
@@ -51,13 +51,13 @@ ${indent}${indent}${indent}<name>${escapeXml(f.name)}</name>
|
|||||||
${indent}${indent}${indent}<description>${escapeXml(f.description)}</description>${
|
${indent}${indent}${indent}<description>${escapeXml(f.description)}</description>${
|
||||||
f.file_locations && f.file_locations.length > 0
|
f.file_locations && f.file_locations.length > 0
|
||||||
? `\n${indent}${indent}${indent}<file_locations>
|
? `\n${indent}${indent}${indent}<file_locations>
|
||||||
${f.file_locations.map((loc) => `${indent}${indent}${indent}${indent}<location>${escapeXml(loc)}</location>`).join("\n")}
|
${f.file_locations.map((loc) => `${indent}${indent}${indent}${indent}<location>${escapeXml(loc)}</location>`).join('\n')}
|
||||||
${indent}${indent}${indent}</file_locations>`
|
${indent}${indent}${indent}</file_locations>`
|
||||||
: ""
|
: ''
|
||||||
}
|
}
|
||||||
${indent}${indent}</feature>`
|
${indent}${indent}</feature>`
|
||||||
)
|
)
|
||||||
.join("\n")}
|
.join('\n')}
|
||||||
${indent}</implemented_features>`;
|
${indent}</implemented_features>`;
|
||||||
|
|
||||||
// Optional sections
|
// Optional sections
|
||||||
@@ -65,7 +65,7 @@ ${indent}</implemented_features>`;
|
|||||||
xml += `
|
xml += `
|
||||||
|
|
||||||
${indent}<additional_requirements>
|
${indent}<additional_requirements>
|
||||||
${spec.additional_requirements.map((r) => `${indent}${indent}<requirement>${escapeXml(r)}</requirement>`).join("\n")}
|
${spec.additional_requirements.map((r) => `${indent}${indent}<requirement>${escapeXml(r)}</requirement>`).join('\n')}
|
||||||
${indent}</additional_requirements>`;
|
${indent}</additional_requirements>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ ${indent}</additional_requirements>`;
|
|||||||
xml += `
|
xml += `
|
||||||
|
|
||||||
${indent}<development_guidelines>
|
${indent}<development_guidelines>
|
||||||
${spec.development_guidelines.map((g) => `${indent}${indent}<guideline>${escapeXml(g)}</guideline>`).join("\n")}
|
${spec.development_guidelines.map((g) => `${indent}${indent}<guideline>${escapeXml(g)}</guideline>`).join('\n')}
|
||||||
${indent}</development_guidelines>`;
|
${indent}</development_guidelines>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ ${indent}${indent}${indent}<status>${escapeXml(r.status)}</status>
|
|||||||
${indent}${indent}${indent}<description>${escapeXml(r.description)}</description>
|
${indent}${indent}${indent}<description>${escapeXml(r.description)}</description>
|
||||||
${indent}${indent}</phase>`
|
${indent}${indent}</phase>`
|
||||||
)
|
)
|
||||||
.join("\n")}
|
.join('\n')}
|
||||||
${indent}</implementation_roadmap>`;
|
${indent}</implementation_roadmap>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Supports API key authentication via header or environment variable.
|
* Supports API key authentication via header or environment variable.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response, NextFunction } from "express";
|
import type { Request, Response, NextFunction } from 'express';
|
||||||
|
|
||||||
// API key from environment (optional - if not set, auth is disabled)
|
// API key from environment (optional - if not set, auth is disabled)
|
||||||
const API_KEY = process.env.AUTOMAKER_API_KEY;
|
const API_KEY = process.env.AUTOMAKER_API_KEY;
|
||||||
@@ -23,12 +23,12 @@ export function authMiddleware(req: Request, res: Response, next: NextFunction):
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for API key in header
|
// Check for API key in header
|
||||||
const providedKey = req.headers["x-api-key"] as string | undefined;
|
const providedKey = req.headers['x-api-key'] as string | undefined;
|
||||||
|
|
||||||
if (!providedKey) {
|
if (!providedKey) {
|
||||||
res.status(401).json({
|
res.status(401).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "Authentication required. Provide X-API-Key header.",
|
error: 'Authentication required. Provide X-API-Key header.',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ export function authMiddleware(req: Request, res: Response, next: NextFunction):
|
|||||||
if (providedKey !== API_KEY) {
|
if (providedKey !== API_KEY) {
|
||||||
res.status(403).json({
|
res.status(403).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "Invalid API key.",
|
error: 'Invalid API key.',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -57,6 +57,6 @@ export function isAuthEnabled(): boolean {
|
|||||||
export function getAuthStatus(): { enabled: boolean; method: string } {
|
export function getAuthStatus(): { enabled: boolean; method: string } {
|
||||||
return {
|
return {
|
||||||
enabled: !!API_KEY,
|
enabled: !!API_KEY,
|
||||||
method: API_KEY ? "api_key" : "none",
|
method: API_KEY ? 'api_key' : 'none',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Event emitter for streaming events to WebSocket clients
|
* Event emitter for streaming events to WebSocket clients
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { EventType, EventCallback } from "@automaker/types";
|
import type { EventType, EventCallback } from '@automaker/types';
|
||||||
|
|
||||||
// Re-export event types from shared package
|
// Re-export event types from shared package
|
||||||
export type { EventType, EventCallback };
|
export type { EventType, EventCallback };
|
||||||
@@ -21,7 +21,7 @@ export function createEventEmitter(): EventEmitter {
|
|||||||
try {
|
try {
|
||||||
callback(type, payload);
|
callback(type, payload);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error in event subscriber:", error);
|
console.error('Error in event subscriber:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* This file exists for backward compatibility with existing imports
|
* This file exists for backward compatibility with existing imports
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { secureFs } from "@automaker/platform";
|
import { secureFs } from '@automaker/platform';
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
access,
|
access,
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
* try-catch block in every route handler
|
* try-catch block in every route handler
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response, NextFunction } from "express";
|
import type { Request, Response, NextFunction } from 'express';
|
||||||
import { validatePath, PathNotAllowedError } from "@automaker/platform";
|
import { validatePath, PathNotAllowedError } from '@automaker/platform';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a middleware that validates specified path parameters in req.body
|
* Creates a middleware that validates specified path parameters in req.body
|
||||||
@@ -24,7 +24,7 @@ export function validatePathParams(...paramNames: string[]) {
|
|||||||
try {
|
try {
|
||||||
for (const paramName of paramNames) {
|
for (const paramName of paramNames) {
|
||||||
// Handle optional parameters (paramName?)
|
// Handle optional parameters (paramName?)
|
||||||
if (paramName.endsWith("?")) {
|
if (paramName.endsWith('?')) {
|
||||||
const actualName = paramName.slice(0, -1);
|
const actualName = paramName.slice(0, -1);
|
||||||
const value = req.body[actualName];
|
const value = req.body[actualName];
|
||||||
if (value) {
|
if (value) {
|
||||||
@@ -34,7 +34,7 @@ export function validatePathParams(...paramNames: string[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle array parameters (paramName[])
|
// Handle array parameters (paramName[])
|
||||||
if (paramName.endsWith("[]")) {
|
if (paramName.endsWith('[]')) {
|
||||||
const actualName = paramName.slice(0, -2);
|
const actualName = paramName.slice(0, -2);
|
||||||
const values = req.body[actualName];
|
const values = req.body[actualName];
|
||||||
if (Array.isArray(values) && values.length > 0) {
|
if (Array.isArray(values) && values.length > 0) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import type {
|
|||||||
InstallationStatus,
|
InstallationStatus,
|
||||||
ValidationResult,
|
ValidationResult,
|
||||||
ModelDefinition,
|
ModelDefinition,
|
||||||
} from "./types.js";
|
} from './types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base provider class that all provider implementations must extend
|
* Base provider class that all provider implementations must extend
|
||||||
@@ -33,9 +33,7 @@ export abstract class BaseProvider {
|
|||||||
* @param options Execution options
|
* @param options Execution options
|
||||||
* @returns AsyncGenerator yielding provider messages
|
* @returns AsyncGenerator yielding provider messages
|
||||||
*/
|
*/
|
||||||
abstract executeQuery(
|
abstract executeQuery(options: ExecuteOptions): AsyncGenerator<ProviderMessage>;
|
||||||
options: ExecuteOptions
|
|
||||||
): AsyncGenerator<ProviderMessage>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect if the provider is installed and configured
|
* Detect if the provider is installed and configured
|
||||||
@@ -59,7 +57,7 @@ export abstract class BaseProvider {
|
|||||||
|
|
||||||
// Base validation (can be overridden)
|
// Base validation (can be overridden)
|
||||||
if (!this.config) {
|
if (!this.config) {
|
||||||
errors.push("Provider config is missing");
|
errors.push('Provider config is missing');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -76,7 +74,7 @@ export abstract class BaseProvider {
|
|||||||
*/
|
*/
|
||||||
supportsFeature(feature: string): boolean {
|
supportsFeature(feature: string): boolean {
|
||||||
// Default implementation - override in subclasses
|
// Default implementation - override in subclasses
|
||||||
const commonFeatures = ["tools", "text"];
|
const commonFeatures = ['tools', 'text'];
|
||||||
return commonFeatures.includes(feature);
|
return commonFeatures.includes(feature);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
* new providers (Cursor, OpenCode, etc.) trivial - just add one line.
|
* new providers (Cursor, OpenCode, etc.) trivial - just add one line.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { BaseProvider } from "./base-provider.js";
|
import { BaseProvider } from './base-provider.js';
|
||||||
import { ClaudeProvider } from "./claude-provider.js";
|
import { ClaudeProvider } from './claude-provider.js';
|
||||||
import type { InstallationStatus } from "./types.js";
|
import type { InstallationStatus } from './types.js';
|
||||||
|
|
||||||
export class ProviderFactory {
|
export class ProviderFactory {
|
||||||
/**
|
/**
|
||||||
@@ -21,10 +21,7 @@ export class ProviderFactory {
|
|||||||
const lowerModel = modelId.toLowerCase();
|
const lowerModel = modelId.toLowerCase();
|
||||||
|
|
||||||
// Claude models (claude-*, opus, sonnet, haiku)
|
// Claude models (claude-*, opus, sonnet, haiku)
|
||||||
if (
|
if (lowerModel.startsWith('claude-') || ['haiku', 'sonnet', 'opus'].includes(lowerModel)) {
|
||||||
lowerModel.startsWith("claude-") ||
|
|
||||||
["haiku", "sonnet", "opus"].includes(lowerModel)
|
|
||||||
) {
|
|
||||||
return new ClaudeProvider();
|
return new ClaudeProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,9 +34,7 @@ export class ProviderFactory {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// Default to Claude for unknown models
|
// Default to Claude for unknown models
|
||||||
console.warn(
|
console.warn(`[ProviderFactory] Unknown model prefix for "${modelId}", defaulting to Claude`);
|
||||||
`[ProviderFactory] Unknown model prefix for "${modelId}", defaulting to Claude`
|
|
||||||
);
|
|
||||||
return new ClaudeProvider();
|
return new ClaudeProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,9 +53,7 @@ export class ProviderFactory {
|
|||||||
*
|
*
|
||||||
* @returns Map of provider name to installation status
|
* @returns Map of provider name to installation status
|
||||||
*/
|
*/
|
||||||
static async checkAllProviders(): Promise<
|
static async checkAllProviders(): Promise<Record<string, InstallationStatus>> {
|
||||||
Record<string, InstallationStatus>
|
|
||||||
> {
|
|
||||||
const providers = this.getAllProviders();
|
const providers = this.getAllProviders();
|
||||||
const statuses: Record<string, InstallationStatus> = {};
|
const statuses: Record<string, InstallationStatus> = {};
|
||||||
|
|
||||||
@@ -83,8 +76,8 @@ export class ProviderFactory {
|
|||||||
const lowerName = name.toLowerCase();
|
const lowerName = name.toLowerCase();
|
||||||
|
|
||||||
switch (lowerName) {
|
switch (lowerName) {
|
||||||
case "claude":
|
case 'claude':
|
||||||
case "anthropic":
|
case 'anthropic':
|
||||||
return new ClaudeProvider();
|
return new ClaudeProvider();
|
||||||
|
|
||||||
// Future providers:
|
// Future providers:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export interface ProviderConfig {
|
|||||||
* Message in conversation history
|
* Message in conversation history
|
||||||
*/
|
*/
|
||||||
export interface ConversationMessage {
|
export interface ConversationMessage {
|
||||||
role: "user" | "assistant";
|
role: 'user' | 'assistant';
|
||||||
content: string | Array<{ type: string; text?: string; source?: object }>;
|
content: string | Array<{ type: string; text?: string; source?: object }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ export interface ExecuteOptions {
|
|||||||
* Content block in a provider message (matches Claude SDK format)
|
* Content block in a provider message (matches Claude SDK format)
|
||||||
*/
|
*/
|
||||||
export interface ContentBlock {
|
export interface ContentBlock {
|
||||||
type: "text" | "tool_use" | "thinking" | "tool_result";
|
type: 'text' | 'tool_use' | 'thinking' | 'tool_result';
|
||||||
text?: string;
|
text?: string;
|
||||||
thinking?: string;
|
thinking?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
@@ -52,11 +52,11 @@ export interface ContentBlock {
|
|||||||
* Message returned by a provider (matches Claude SDK streaming format)
|
* Message returned by a provider (matches Claude SDK streaming format)
|
||||||
*/
|
*/
|
||||||
export interface ProviderMessage {
|
export interface ProviderMessage {
|
||||||
type: "assistant" | "user" | "error" | "result";
|
type: 'assistant' | 'user' | 'error' | 'result';
|
||||||
subtype?: "success" | "error";
|
subtype?: 'success' | 'error';
|
||||||
session_id?: string;
|
session_id?: string;
|
||||||
message?: {
|
message?: {
|
||||||
role: "user" | "assistant";
|
role: 'user' | 'assistant';
|
||||||
content: ContentBlock[];
|
content: ContentBlock[];
|
||||||
};
|
};
|
||||||
result?: string;
|
result?: string;
|
||||||
@@ -71,7 +71,7 @@ export interface InstallationStatus {
|
|||||||
installed: boolean;
|
installed: boolean;
|
||||||
path?: string;
|
path?: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
method?: "cli" | "npm" | "brew" | "sdk";
|
method?: 'cli' | 'npm' | 'brew' | 'sdk';
|
||||||
hasApiKey?: boolean;
|
hasApiKey?: boolean;
|
||||||
authenticated?: boolean;
|
authenticated?: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
@@ -99,6 +99,6 @@ export interface ModelDefinition {
|
|||||||
maxOutputTokens?: number;
|
maxOutputTokens?: number;
|
||||||
supportsVision?: boolean;
|
supportsVision?: boolean;
|
||||||
supportsTools?: boolean;
|
supportsTools?: boolean;
|
||||||
tier?: "basic" | "standard" | "premium";
|
tier?: 'basic' | 'standard' | 'premium';
|
||||||
default?: boolean;
|
default?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,10 @@
|
|||||||
* Common utilities for agent routes
|
* Common utilities for agent routes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import {
|
import { getErrorMessage as getErrorMessageShared, createLogError } from '../common.js';
|
||||||
getErrorMessage as getErrorMessageShared,
|
|
||||||
createLogError,
|
|
||||||
} from "../common.js";
|
|
||||||
|
|
||||||
const logger = createLogger("Agent");
|
const logger = createLogger('Agent');
|
||||||
|
|
||||||
// Re-export shared utilities
|
// Re-export shared utilities
|
||||||
export { getErrorMessageShared as getErrorMessage };
|
export { getErrorMessageShared as getErrorMessage };
|
||||||
|
|||||||
@@ -2,29 +2,30 @@
|
|||||||
* Agent routes - HTTP API for Claude agent interactions
|
* Agent routes - HTTP API for Claude agent interactions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router } from "express";
|
import { Router } from 'express';
|
||||||
import { AgentService } from "../../services/agent-service.js";
|
import { AgentService } from '../../services/agent-service.js';
|
||||||
import type { EventEmitter } from "../../lib/events.js";
|
import type { EventEmitter } from '../../lib/events.js';
|
||||||
import { validatePathParams } from "../../middleware/validate-paths.js";
|
import { validatePathParams } from '../../middleware/validate-paths.js';
|
||||||
import { createStartHandler } from "./routes/start.js";
|
import { createStartHandler } from './routes/start.js';
|
||||||
import { createSendHandler } from "./routes/send.js";
|
import { createSendHandler } from './routes/send.js';
|
||||||
import { createHistoryHandler } from "./routes/history.js";
|
import { createHistoryHandler } from './routes/history.js';
|
||||||
import { createStopHandler } from "./routes/stop.js";
|
import { createStopHandler } from './routes/stop.js';
|
||||||
import { createClearHandler } from "./routes/clear.js";
|
import { createClearHandler } from './routes/clear.js';
|
||||||
import { createModelHandler } from "./routes/model.js";
|
import { createModelHandler } from './routes/model.js';
|
||||||
|
|
||||||
export function createAgentRoutes(
|
export function createAgentRoutes(agentService: AgentService, _events: EventEmitter): Router {
|
||||||
agentService: AgentService,
|
|
||||||
_events: EventEmitter
|
|
||||||
): Router {
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post("/start", validatePathParams("workingDirectory?"), createStartHandler(agentService));
|
router.post('/start', validatePathParams('workingDirectory?'), createStartHandler(agentService));
|
||||||
router.post("/send", validatePathParams("workingDirectory?", "imagePaths[]"), createSendHandler(agentService));
|
router.post(
|
||||||
router.post("/history", createHistoryHandler(agentService));
|
'/send',
|
||||||
router.post("/stop", createStopHandler(agentService));
|
validatePathParams('workingDirectory?', 'imagePaths[]'),
|
||||||
router.post("/clear", createClearHandler(agentService));
|
createSendHandler(agentService)
|
||||||
router.post("/model", createModelHandler(agentService));
|
);
|
||||||
|
router.post('/history', createHistoryHandler(agentService));
|
||||||
|
router.post('/stop', createStopHandler(agentService));
|
||||||
|
router.post('/clear', createClearHandler(agentService));
|
||||||
|
router.post('/model', createModelHandler(agentService));
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* POST /clear endpoint - Clear conversation
|
* POST /clear endpoint - Clear conversation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { AgentService } from "../../../services/agent-service.js";
|
import { AgentService } from '../../../services/agent-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createClearHandler(agentService: AgentService) {
|
export function createClearHandler(agentService: AgentService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -12,16 +12,14 @@ export function createClearHandler(agentService: AgentService) {
|
|||||||
const { sessionId } = req.body as { sessionId: string };
|
const { sessionId } = req.body as { sessionId: string };
|
||||||
|
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
res
|
res.status(400).json({ success: false, error: 'sessionId is required' });
|
||||||
.status(400)
|
|
||||||
.json({ success: false, error: "sessionId is required" });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await agentService.clearSession(sessionId);
|
const result = await agentService.clearSession(sessionId);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Clear session failed");
|
logError(error, 'Clear session failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* POST /history endpoint - Get conversation history
|
* POST /history endpoint - Get conversation history
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { AgentService } from "../../../services/agent-service.js";
|
import { AgentService } from '../../../services/agent-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createHistoryHandler(agentService: AgentService) {
|
export function createHistoryHandler(agentService: AgentService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -12,16 +12,14 @@ export function createHistoryHandler(agentService: AgentService) {
|
|||||||
const { sessionId } = req.body as { sessionId: string };
|
const { sessionId } = req.body as { sessionId: string };
|
||||||
|
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
res
|
res.status(400).json({ success: false, error: 'sessionId is required' });
|
||||||
.status(400)
|
|
||||||
.json({ success: false, error: "sessionId is required" });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = agentService.getHistory(sessionId);
|
const result = agentService.getHistory(sessionId);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Get history failed");
|
logError(error, 'Get history failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* POST /model endpoint - Set session model
|
* POST /model endpoint - Set session model
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { AgentService } from "../../../services/agent-service.js";
|
import { AgentService } from '../../../services/agent-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createModelHandler(agentService: AgentService) {
|
export function createModelHandler(agentService: AgentService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -15,16 +15,14 @@ export function createModelHandler(agentService: AgentService) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!sessionId || !model) {
|
if (!sessionId || !model) {
|
||||||
res
|
res.status(400).json({ success: false, error: 'sessionId and model are required' });
|
||||||
.status(400)
|
|
||||||
.json({ success: false, error: "sessionId and model are required" });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await agentService.setSessionModel(sessionId, model);
|
const result = await agentService.setSessionModel(sessionId, model);
|
||||||
res.json({ success: result });
|
res.json({ success: result });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Set session model failed");
|
logError(error, 'Set session model failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,28 +2,27 @@
|
|||||||
* POST /send endpoint - Send a message
|
* POST /send endpoint - Send a message
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { AgentService } from "../../../services/agent-service.js";
|
import { AgentService } from '../../../services/agent-service.js';
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
const logger = createLogger("Agent");
|
const logger = createLogger('Agent');
|
||||||
|
|
||||||
export function createSendHandler(agentService: AgentService) {
|
export function createSendHandler(agentService: AgentService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { sessionId, message, workingDirectory, imagePaths, model } =
|
const { sessionId, message, workingDirectory, imagePaths, model } = req.body as {
|
||||||
req.body as {
|
sessionId: string;
|
||||||
sessionId: string;
|
message: string;
|
||||||
message: string;
|
workingDirectory?: string;
|
||||||
workingDirectory?: string;
|
imagePaths?: string[];
|
||||||
imagePaths?: string[];
|
model?: string;
|
||||||
model?: string;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
if (!sessionId || !message) {
|
if (!sessionId || !message) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "sessionId and message are required",
|
error: 'sessionId and message are required',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -38,13 +37,13 @@ export function createSendHandler(agentService: AgentService) {
|
|||||||
model,
|
model,
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logError(error, "Send message failed (background)");
|
logError(error, 'Send message failed (background)');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Return immediately - responses come via WebSocket
|
// Return immediately - responses come via WebSocket
|
||||||
res.json({ success: true, message: "Message sent" });
|
res.json({ success: true, message: 'Message sent' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Send message failed");
|
logError(error, 'Send message failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
* POST /start endpoint - Start a conversation
|
* POST /start endpoint - Start a conversation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { AgentService } from "../../../services/agent-service.js";
|
import { AgentService } from '../../../services/agent-service.js';
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
const logger = createLogger("Agent");
|
const logger = createLogger('Agent');
|
||||||
|
|
||||||
export function createStartHandler(agentService: AgentService) {
|
export function createStartHandler(agentService: AgentService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -17,9 +17,7 @@ export function createStartHandler(agentService: AgentService) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
res
|
res.status(400).json({ success: false, error: 'sessionId is required' });
|
||||||
.status(400)
|
|
||||||
.json({ success: false, error: "sessionId is required" });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +28,7 @@ export function createStartHandler(agentService: AgentService) {
|
|||||||
|
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Start conversation failed");
|
logError(error, 'Start conversation failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* POST /stop endpoint - Stop execution
|
* POST /stop endpoint - Stop execution
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { AgentService } from "../../../services/agent-service.js";
|
import { AgentService } from '../../../services/agent-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createStopHandler(agentService: AgentService) {
|
export function createStopHandler(agentService: AgentService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -12,16 +12,14 @@ export function createStopHandler(agentService: AgentService) {
|
|||||||
const { sessionId } = req.body as { sessionId: string };
|
const { sessionId } = req.body as { sessionId: string };
|
||||||
|
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
res
|
res.status(400).json({ success: false, error: 'sessionId is required' });
|
||||||
.status(400)
|
|
||||||
.json({ success: false, error: "sessionId is required" });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await agentService.stopExecution(sessionId);
|
const result = await agentService.stopExecution(sessionId);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Stop execution failed");
|
logError(error, 'Stop execution failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* Common utilities and state management for spec regeneration
|
* Common utilities and state management for spec regeneration
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
|
|
||||||
const logger = createLogger("SpecRegeneration");
|
const logger = createLogger('SpecRegeneration');
|
||||||
|
|
||||||
// Shared state for tracking generation status - private
|
// Shared state for tracking generation status - private
|
||||||
let isRunning = false;
|
let isRunning = false;
|
||||||
@@ -23,10 +23,7 @@ export function getSpecRegenerationStatus(): {
|
|||||||
/**
|
/**
|
||||||
* Set the running state and abort controller
|
* Set the running state and abort controller
|
||||||
*/
|
*/
|
||||||
export function setRunningState(
|
export function setRunningState(running: boolean, controller: AbortController | null = null): void {
|
||||||
running: boolean,
|
|
||||||
controller: AbortController | null = null
|
|
||||||
): void {
|
|
||||||
isRunning = running;
|
isRunning = running;
|
||||||
currentAbortController = controller;
|
currentAbortController = controller;
|
||||||
}
|
}
|
||||||
@@ -40,14 +37,12 @@ export function logAuthStatus(context: string): void {
|
|||||||
logger.info(`${context} - Auth Status:`);
|
logger.info(`${context} - Auth Status:`);
|
||||||
logger.info(
|
logger.info(
|
||||||
` ANTHROPIC_API_KEY: ${
|
` ANTHROPIC_API_KEY: ${
|
||||||
hasApiKey
|
hasApiKey ? 'SET (' + process.env.ANTHROPIC_API_KEY?.substring(0, 20) + '...)' : 'NOT SET'
|
||||||
? "SET (" + process.env.ANTHROPIC_API_KEY?.substring(0, 20) + "...)"
|
|
||||||
: "NOT SET"
|
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!hasApiKey) {
|
if (!hasApiKey) {
|
||||||
logger.warn("⚠️ WARNING: No authentication configured! SDK will fail.");
|
logger.warn('⚠️ WARNING: No authentication configured! SDK will fail.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,16 +51,13 @@ export function logAuthStatus(context: string): void {
|
|||||||
*/
|
*/
|
||||||
export function logError(error: unknown, context: string): void {
|
export function logError(error: unknown, context: string): void {
|
||||||
logger.error(`❌ ${context}:`);
|
logger.error(`❌ ${context}:`);
|
||||||
logger.error("Error name:", (error as any)?.name);
|
logger.error('Error name:', (error as any)?.name);
|
||||||
logger.error("Error message:", (error as Error)?.message);
|
logger.error('Error message:', (error as Error)?.message);
|
||||||
logger.error("Error stack:", (error as Error)?.stack);
|
logger.error('Error stack:', (error as Error)?.stack);
|
||||||
logger.error(
|
logger.error('Full error object:', JSON.stringify(error, Object.getOwnPropertyNames(error), 2));
|
||||||
"Full error object:",
|
|
||||||
JSON.stringify(error, Object.getOwnPropertyNames(error), 2)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
import { getErrorMessage as getErrorMessageShared } from "../common.js";
|
import { getErrorMessage as getErrorMessageShared } from '../common.js';
|
||||||
|
|
||||||
// Re-export shared utility
|
// Re-export shared utility
|
||||||
export { getErrorMessageShared as getErrorMessage };
|
export { getErrorMessageShared as getErrorMessage };
|
||||||
|
|||||||
@@ -2,25 +2,22 @@
|
|||||||
* Spec Regeneration routes - HTTP API for AI-powered spec generation
|
* Spec Regeneration routes - HTTP API for AI-powered spec generation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router } from "express";
|
import { Router } from 'express';
|
||||||
import type { EventEmitter } from "../../lib/events.js";
|
import type { EventEmitter } from '../../lib/events.js';
|
||||||
import { createCreateHandler } from "./routes/create.js";
|
import { createCreateHandler } from './routes/create.js';
|
||||||
import { createGenerateHandler } from "./routes/generate.js";
|
import { createGenerateHandler } from './routes/generate.js';
|
||||||
import { createGenerateFeaturesHandler } from "./routes/generate-features.js";
|
import { createGenerateFeaturesHandler } from './routes/generate-features.js';
|
||||||
import { createStopHandler } from "./routes/stop.js";
|
import { createStopHandler } from './routes/stop.js';
|
||||||
import { createStatusHandler } from "./routes/status.js";
|
import { createStatusHandler } from './routes/status.js';
|
||||||
|
|
||||||
export function createSpecRegenerationRoutes(events: EventEmitter): Router {
|
export function createSpecRegenerationRoutes(events: EventEmitter): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post("/create", createCreateHandler(events));
|
router.post('/create', createCreateHandler(events));
|
||||||
router.post("/generate", createGenerateHandler(events));
|
router.post('/generate', createGenerateHandler(events));
|
||||||
router.post("/generate-features", createGenerateFeaturesHandler(events));
|
router.post('/generate-features', createGenerateFeaturesHandler(events));
|
||||||
router.post("/stop", createStopHandler());
|
router.post('/stop', createStopHandler());
|
||||||
router.get("/status", createStatusHandler());
|
router.get('/status', createStatusHandler());
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,24 +2,24 @@
|
|||||||
* POST /create endpoint - Create project spec from overview
|
* POST /create endpoint - Create project spec from overview
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { EventEmitter } from "../../../lib/events.js";
|
import type { EventEmitter } from '../../../lib/events.js';
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import {
|
import {
|
||||||
getSpecRegenerationStatus,
|
getSpecRegenerationStatus,
|
||||||
setRunningState,
|
setRunningState,
|
||||||
logAuthStatus,
|
logAuthStatus,
|
||||||
logError,
|
logError,
|
||||||
getErrorMessage,
|
getErrorMessage,
|
||||||
} from "../common.js";
|
} from '../common.js';
|
||||||
import { generateSpec } from "../generate-spec.js";
|
import { generateSpec } from '../generate-spec.js';
|
||||||
|
|
||||||
const logger = createLogger("SpecRegeneration");
|
const logger = createLogger('SpecRegeneration');
|
||||||
|
|
||||||
export function createCreateHandler(events: EventEmitter) {
|
export function createCreateHandler(events: EventEmitter) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
logger.info("========== /create endpoint called ==========");
|
logger.info('========== /create endpoint called ==========');
|
||||||
logger.debug("Request body:", JSON.stringify(req.body, null, 2));
|
logger.debug('Request body:', JSON.stringify(req.body, null, 2));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { projectPath, projectOverview, generateFeatures, analyzeProject, maxFeatures } =
|
const { projectPath, projectOverview, generateFeatures, analyzeProject, maxFeatures } =
|
||||||
@@ -31,37 +31,34 @@ export function createCreateHandler(events: EventEmitter) {
|
|||||||
maxFeatures?: number;
|
maxFeatures?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.debug("Parsed params:");
|
logger.debug('Parsed params:');
|
||||||
logger.debug(" projectPath:", projectPath);
|
logger.debug(' projectPath:', projectPath);
|
||||||
logger.debug(
|
logger.debug(' projectOverview length:', `${projectOverview?.length || 0} chars`);
|
||||||
" projectOverview length:",
|
logger.debug(' generateFeatures:', generateFeatures);
|
||||||
`${projectOverview?.length || 0} chars`
|
logger.debug(' analyzeProject:', analyzeProject);
|
||||||
);
|
logger.debug(' maxFeatures:', maxFeatures);
|
||||||
logger.debug(" generateFeatures:", generateFeatures);
|
|
||||||
logger.debug(" analyzeProject:", analyzeProject);
|
|
||||||
logger.debug(" maxFeatures:", maxFeatures);
|
|
||||||
|
|
||||||
if (!projectPath || !projectOverview) {
|
if (!projectPath || !projectOverview) {
|
||||||
logger.error("Missing required parameters");
|
logger.error('Missing required parameters');
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "projectPath and projectOverview required",
|
error: 'projectPath and projectOverview required',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isRunning } = getSpecRegenerationStatus();
|
const { isRunning } = getSpecRegenerationStatus();
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
logger.warn("Generation already running, rejecting request");
|
logger.warn('Generation already running, rejecting request');
|
||||||
res.json({ success: false, error: "Spec generation already running" });
|
res.json({ success: false, error: 'Spec generation already running' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logAuthStatus("Before starting generation");
|
logAuthStatus('Before starting generation');
|
||||||
|
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
setRunningState(true, abortController);
|
setRunningState(true, abortController);
|
||||||
logger.info("Starting background generation task...");
|
logger.info('Starting background generation task...');
|
||||||
|
|
||||||
// Start generation in background
|
// Start generation in background
|
||||||
generateSpec(
|
generateSpec(
|
||||||
@@ -74,24 +71,22 @@ export function createCreateHandler(events: EventEmitter) {
|
|||||||
maxFeatures
|
maxFeatures
|
||||||
)
|
)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logError(error, "Generation failed with error");
|
logError(error, 'Generation failed with error');
|
||||||
events.emit("spec-regeneration:event", {
|
events.emit('spec-regeneration:event', {
|
||||||
type: "spec_regeneration_error",
|
type: 'spec_regeneration_error',
|
||||||
error: getErrorMessage(error),
|
error: getErrorMessage(error),
|
||||||
projectPath: projectPath,
|
projectPath: projectPath,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
logger.info("Generation task finished (success or error)");
|
logger.info('Generation task finished (success or error)');
|
||||||
setRunningState(false, null);
|
setRunningState(false, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info(
|
logger.info('Returning success response (generation running in background)');
|
||||||
"Returning success response (generation running in background)"
|
|
||||||
);
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Create spec route handler failed");
|
logError(error, 'Create spec route handler failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,24 +2,24 @@
|
|||||||
* POST /generate-features endpoint - Generate features from existing spec
|
* POST /generate-features endpoint - Generate features from existing spec
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { EventEmitter } from "../../../lib/events.js";
|
import type { EventEmitter } from '../../../lib/events.js';
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import {
|
import {
|
||||||
getSpecRegenerationStatus,
|
getSpecRegenerationStatus,
|
||||||
setRunningState,
|
setRunningState,
|
||||||
logAuthStatus,
|
logAuthStatus,
|
||||||
logError,
|
logError,
|
||||||
getErrorMessage,
|
getErrorMessage,
|
||||||
} from "../common.js";
|
} from '../common.js';
|
||||||
import { generateFeaturesFromSpec } from "../generate-features-from-spec.js";
|
import { generateFeaturesFromSpec } from '../generate-features-from-spec.js';
|
||||||
|
|
||||||
const logger = createLogger("SpecRegeneration");
|
const logger = createLogger('SpecRegeneration');
|
||||||
|
|
||||||
export function createGenerateFeaturesHandler(events: EventEmitter) {
|
export function createGenerateFeaturesHandler(events: EventEmitter) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
logger.info("========== /generate-features endpoint called ==========");
|
logger.info('========== /generate-features endpoint called ==========');
|
||||||
logger.debug("Request body:", JSON.stringify(req.body, null, 2));
|
logger.debug('Request body:', JSON.stringify(req.body, null, 2));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { projectPath, maxFeatures } = req.body as {
|
const { projectPath, maxFeatures } = req.body as {
|
||||||
@@ -27,52 +27,45 @@ export function createGenerateFeaturesHandler(events: EventEmitter) {
|
|||||||
maxFeatures?: number;
|
maxFeatures?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.debug("projectPath:", projectPath);
|
logger.debug('projectPath:', projectPath);
|
||||||
logger.debug("maxFeatures:", maxFeatures);
|
logger.debug('maxFeatures:', maxFeatures);
|
||||||
|
|
||||||
if (!projectPath) {
|
if (!projectPath) {
|
||||||
logger.error("Missing projectPath parameter");
|
logger.error('Missing projectPath parameter');
|
||||||
res.status(400).json({ success: false, error: "projectPath required" });
|
res.status(400).json({ success: false, error: 'projectPath required' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isRunning } = getSpecRegenerationStatus();
|
const { isRunning } = getSpecRegenerationStatus();
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
logger.warn("Generation already running, rejecting request");
|
logger.warn('Generation already running, rejecting request');
|
||||||
res.json({ success: false, error: "Generation already running" });
|
res.json({ success: false, error: 'Generation already running' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logAuthStatus("Before starting feature generation");
|
logAuthStatus('Before starting feature generation');
|
||||||
|
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
setRunningState(true, abortController);
|
setRunningState(true, abortController);
|
||||||
logger.info("Starting background feature generation task...");
|
logger.info('Starting background feature generation task...');
|
||||||
|
|
||||||
generateFeaturesFromSpec(
|
generateFeaturesFromSpec(projectPath, events, abortController, maxFeatures)
|
||||||
projectPath,
|
|
||||||
events,
|
|
||||||
abortController,
|
|
||||||
maxFeatures
|
|
||||||
)
|
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logError(error, "Feature generation failed with error");
|
logError(error, 'Feature generation failed with error');
|
||||||
events.emit("spec-regeneration:event", {
|
events.emit('spec-regeneration:event', {
|
||||||
type: "features_error",
|
type: 'features_error',
|
||||||
error: getErrorMessage(error),
|
error: getErrorMessage(error),
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
logger.info("Feature generation task finished (success or error)");
|
logger.info('Feature generation task finished (success or error)');
|
||||||
setRunningState(false, null);
|
setRunningState(false, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info(
|
logger.info('Returning success response (generation running in background)');
|
||||||
"Returning success response (generation running in background)"
|
|
||||||
);
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Generate features route handler failed");
|
logError(error, 'Generate features route handler failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,71 +2,63 @@
|
|||||||
* POST /generate endpoint - Generate spec from project definition
|
* POST /generate endpoint - Generate spec from project definition
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { EventEmitter } from "../../../lib/events.js";
|
import type { EventEmitter } from '../../../lib/events.js';
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import {
|
import {
|
||||||
getSpecRegenerationStatus,
|
getSpecRegenerationStatus,
|
||||||
setRunningState,
|
setRunningState,
|
||||||
logAuthStatus,
|
logAuthStatus,
|
||||||
logError,
|
logError,
|
||||||
getErrorMessage,
|
getErrorMessage,
|
||||||
} from "../common.js";
|
} from '../common.js';
|
||||||
import { generateSpec } from "../generate-spec.js";
|
import { generateSpec } from '../generate-spec.js';
|
||||||
|
|
||||||
const logger = createLogger("SpecRegeneration");
|
const logger = createLogger('SpecRegeneration');
|
||||||
|
|
||||||
export function createGenerateHandler(events: EventEmitter) {
|
export function createGenerateHandler(events: EventEmitter) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
logger.info("========== /generate endpoint called ==========");
|
logger.info('========== /generate endpoint called ==========');
|
||||||
logger.debug("Request body:", JSON.stringify(req.body, null, 2));
|
logger.debug('Request body:', JSON.stringify(req.body, null, 2));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const { projectPath, projectDefinition, generateFeatures, analyzeProject, maxFeatures } =
|
||||||
projectPath,
|
req.body as {
|
||||||
projectDefinition,
|
projectPath: string;
|
||||||
generateFeatures,
|
projectDefinition: string;
|
||||||
analyzeProject,
|
generateFeatures?: boolean;
|
||||||
maxFeatures,
|
analyzeProject?: boolean;
|
||||||
} = req.body as {
|
maxFeatures?: number;
|
||||||
projectPath: string;
|
};
|
||||||
projectDefinition: string;
|
|
||||||
generateFeatures?: boolean;
|
|
||||||
analyzeProject?: boolean;
|
|
||||||
maxFeatures?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.debug("Parsed params:");
|
logger.debug('Parsed params:');
|
||||||
logger.debug(" projectPath:", projectPath);
|
logger.debug(' projectPath:', projectPath);
|
||||||
logger.debug(
|
logger.debug(' projectDefinition length:', `${projectDefinition?.length || 0} chars`);
|
||||||
" projectDefinition length:",
|
logger.debug(' generateFeatures:', generateFeatures);
|
||||||
`${projectDefinition?.length || 0} chars`
|
logger.debug(' analyzeProject:', analyzeProject);
|
||||||
);
|
logger.debug(' maxFeatures:', maxFeatures);
|
||||||
logger.debug(" generateFeatures:", generateFeatures);
|
|
||||||
logger.debug(" analyzeProject:", analyzeProject);
|
|
||||||
logger.debug(" maxFeatures:", maxFeatures);
|
|
||||||
|
|
||||||
if (!projectPath || !projectDefinition) {
|
if (!projectPath || !projectDefinition) {
|
||||||
logger.error("Missing required parameters");
|
logger.error('Missing required parameters');
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "projectPath and projectDefinition required",
|
error: 'projectPath and projectDefinition required',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isRunning } = getSpecRegenerationStatus();
|
const { isRunning } = getSpecRegenerationStatus();
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
logger.warn("Generation already running, rejecting request");
|
logger.warn('Generation already running, rejecting request');
|
||||||
res.json({ success: false, error: "Spec generation already running" });
|
res.json({ success: false, error: 'Spec generation already running' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logAuthStatus("Before starting generation");
|
logAuthStatus('Before starting generation');
|
||||||
|
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
setRunningState(true, abortController);
|
setRunningState(true, abortController);
|
||||||
logger.info("Starting background generation task...");
|
logger.info('Starting background generation task...');
|
||||||
|
|
||||||
generateSpec(
|
generateSpec(
|
||||||
projectPath,
|
projectPath,
|
||||||
@@ -78,24 +70,22 @@ export function createGenerateHandler(events: EventEmitter) {
|
|||||||
maxFeatures
|
maxFeatures
|
||||||
)
|
)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logError(error, "Generation failed with error");
|
logError(error, 'Generation failed with error');
|
||||||
events.emit("spec-regeneration:event", {
|
events.emit('spec-regeneration:event', {
|
||||||
type: "spec_regeneration_error",
|
type: 'spec_regeneration_error',
|
||||||
error: getErrorMessage(error),
|
error: getErrorMessage(error),
|
||||||
projectPath: projectPath,
|
projectPath: projectPath,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
logger.info("Generation task finished (success or error)");
|
logger.info('Generation task finished (success or error)');
|
||||||
setRunningState(false, null);
|
setRunningState(false, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info(
|
logger.info('Returning success response (generation running in background)');
|
||||||
"Returning success response (generation running in background)"
|
|
||||||
);
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Generate spec route handler failed");
|
logError(error, 'Generate spec route handler failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
* GET /status endpoint - Get generation status
|
* GET /status endpoint - Get generation status
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { getSpecRegenerationStatus, getErrorMessage } from "../common.js";
|
import { getSpecRegenerationStatus, getErrorMessage } from '../common.js';
|
||||||
|
|
||||||
export function createStatusHandler() {
|
export function createStatusHandler() {
|
||||||
return async (_req: Request, res: Response): Promise<void> => {
|
return async (_req: Request, res: Response): Promise<void> => {
|
||||||
|
|||||||
@@ -2,12 +2,8 @@
|
|||||||
* POST /stop endpoint - Stop generation
|
* POST /stop endpoint - Stop generation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import {
|
import { getSpecRegenerationStatus, setRunningState, getErrorMessage } from '../common.js';
|
||||||
getSpecRegenerationStatus,
|
|
||||||
setRunningState,
|
|
||||||
getErrorMessage,
|
|
||||||
} from "../common.js";
|
|
||||||
|
|
||||||
export function createStopHandler() {
|
export function createStopHandler() {
|
||||||
return async (_req: Request, res: Response): Promise<void> => {
|
return async (_req: Request, res: Response): Promise<void> => {
|
||||||
|
|||||||
@@ -2,13 +2,10 @@
|
|||||||
* Common utilities for auto-mode routes
|
* Common utilities for auto-mode routes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import {
|
import { getErrorMessage as getErrorMessageShared, createLogError } from '../common.js';
|
||||||
getErrorMessage as getErrorMessageShared,
|
|
||||||
createLogError,
|
|
||||||
} from "../common.js";
|
|
||||||
|
|
||||||
const logger = createLogger("AutoMode");
|
const logger = createLogger('AutoMode');
|
||||||
|
|
||||||
// Re-export shared utilities
|
// Re-export shared utilities
|
||||||
export { getErrorMessageShared as getErrorMessage };
|
export { getErrorMessageShared as getErrorMessage };
|
||||||
|
|||||||
@@ -4,37 +4,65 @@
|
|||||||
* Uses the AutoModeService for real feature execution with Claude Agent SDK
|
* Uses the AutoModeService for real feature execution with Claude Agent SDK
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router } from "express";
|
import { Router } from 'express';
|
||||||
import type { AutoModeService } from "../../services/auto-mode-service.js";
|
import type { AutoModeService } from '../../services/auto-mode-service.js';
|
||||||
import { validatePathParams } from "../../middleware/validate-paths.js";
|
import { validatePathParams } from '../../middleware/validate-paths.js';
|
||||||
import { createStopFeatureHandler } from "./routes/stop-feature.js";
|
import { createStopFeatureHandler } from './routes/stop-feature.js';
|
||||||
import { createStatusHandler } from "./routes/status.js";
|
import { createStatusHandler } from './routes/status.js';
|
||||||
import { createRunFeatureHandler } from "./routes/run-feature.js";
|
import { createRunFeatureHandler } from './routes/run-feature.js';
|
||||||
import { createVerifyFeatureHandler } from "./routes/verify-feature.js";
|
import { createVerifyFeatureHandler } from './routes/verify-feature.js';
|
||||||
import { createResumeFeatureHandler } from "./routes/resume-feature.js";
|
import { createResumeFeatureHandler } from './routes/resume-feature.js';
|
||||||
import { createContextExistsHandler } from "./routes/context-exists.js";
|
import { createContextExistsHandler } from './routes/context-exists.js';
|
||||||
import { createAnalyzeProjectHandler } from "./routes/analyze-project.js";
|
import { createAnalyzeProjectHandler } from './routes/analyze-project.js';
|
||||||
import { createFollowUpFeatureHandler } from "./routes/follow-up-feature.js";
|
import { createFollowUpFeatureHandler } from './routes/follow-up-feature.js';
|
||||||
import { createCommitFeatureHandler } from "./routes/commit-feature.js";
|
import { createCommitFeatureHandler } from './routes/commit-feature.js';
|
||||||
import { createApprovePlanHandler } from "./routes/approve-plan.js";
|
import { createApprovePlanHandler } from './routes/approve-plan.js';
|
||||||
|
|
||||||
export function createAutoModeRoutes(autoModeService: AutoModeService): Router {
|
export function createAutoModeRoutes(autoModeService: AutoModeService): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post("/stop-feature", createStopFeatureHandler(autoModeService));
|
router.post('/stop-feature', createStopFeatureHandler(autoModeService));
|
||||||
router.post("/status", validatePathParams("projectPath?"), createStatusHandler(autoModeService));
|
router.post('/status', validatePathParams('projectPath?'), createStatusHandler(autoModeService));
|
||||||
router.post("/run-feature", validatePathParams("projectPath"), createRunFeatureHandler(autoModeService));
|
|
||||||
router.post("/verify-feature", validatePathParams("projectPath"), createVerifyFeatureHandler(autoModeService));
|
|
||||||
router.post("/resume-feature", validatePathParams("projectPath"), createResumeFeatureHandler(autoModeService));
|
|
||||||
router.post("/context-exists", validatePathParams("projectPath"), createContextExistsHandler(autoModeService));
|
|
||||||
router.post("/analyze-project", validatePathParams("projectPath"), createAnalyzeProjectHandler(autoModeService));
|
|
||||||
router.post(
|
router.post(
|
||||||
"/follow-up-feature",
|
'/run-feature',
|
||||||
validatePathParams("projectPath", "imagePaths[]"),
|
validatePathParams('projectPath'),
|
||||||
|
createRunFeatureHandler(autoModeService)
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/verify-feature',
|
||||||
|
validatePathParams('projectPath'),
|
||||||
|
createVerifyFeatureHandler(autoModeService)
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/resume-feature',
|
||||||
|
validatePathParams('projectPath'),
|
||||||
|
createResumeFeatureHandler(autoModeService)
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/context-exists',
|
||||||
|
validatePathParams('projectPath'),
|
||||||
|
createContextExistsHandler(autoModeService)
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/analyze-project',
|
||||||
|
validatePathParams('projectPath'),
|
||||||
|
createAnalyzeProjectHandler(autoModeService)
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/follow-up-feature',
|
||||||
|
validatePathParams('projectPath', 'imagePaths[]'),
|
||||||
createFollowUpFeatureHandler(autoModeService)
|
createFollowUpFeatureHandler(autoModeService)
|
||||||
);
|
);
|
||||||
router.post("/commit-feature", validatePathParams("projectPath", "worktreePath?"), createCommitFeatureHandler(autoModeService));
|
router.post(
|
||||||
router.post("/approve-plan", validatePathParams("projectPath"), createApprovePlanHandler(autoModeService));
|
'/commit-feature',
|
||||||
|
validatePathParams('projectPath', 'worktreePath?'),
|
||||||
|
createCommitFeatureHandler(autoModeService)
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/approve-plan',
|
||||||
|
validatePathParams('projectPath'),
|
||||||
|
createApprovePlanHandler(autoModeService)
|
||||||
|
);
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
* POST /analyze-project endpoint - Analyze project
|
* POST /analyze-project endpoint - Analyze project
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { AutoModeService } from "../../../services/auto-mode-service.js";
|
import type { AutoModeService } from '../../../services/auto-mode-service.js';
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
const logger = createLogger("AutoMode");
|
const logger = createLogger('AutoMode');
|
||||||
|
|
||||||
export function createAnalyzeProjectHandler(autoModeService: AutoModeService) {
|
export function createAnalyzeProjectHandler(autoModeService: AutoModeService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -15,9 +15,7 @@ export function createAnalyzeProjectHandler(autoModeService: AutoModeService) {
|
|||||||
const { projectPath } = req.body as { projectPath: string };
|
const { projectPath } = req.body as { projectPath: string };
|
||||||
|
|
||||||
if (!projectPath) {
|
if (!projectPath) {
|
||||||
res
|
res.status(400).json({ success: false, error: 'projectPath is required' });
|
||||||
.status(400)
|
|
||||||
.json({ success: false, error: "projectPath is required" });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,9 +24,9 @@ export function createAnalyzeProjectHandler(autoModeService: AutoModeService) {
|
|||||||
logger.error(`[AutoMode] Project analysis error:`, error);
|
logger.error(`[AutoMode] Project analysis error:`, error);
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json({ success: true, message: "Project analysis started" });
|
res.json({ success: true, message: 'Project analysis started' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Analyze project failed");
|
logError(error, 'Analyze project failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
* POST /approve-plan endpoint - Approve or reject a generated plan/spec
|
* POST /approve-plan endpoint - Approve or reject a generated plan/spec
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { AutoModeService } from "../../../services/auto-mode-service.js";
|
import type { AutoModeService } from '../../../services/auto-mode-service.js';
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
const logger = createLogger("AutoMode");
|
const logger = createLogger('AutoMode');
|
||||||
|
|
||||||
export function createApprovePlanHandler(autoModeService: AutoModeService) {
|
export function createApprovePlanHandler(autoModeService: AutoModeService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -23,15 +23,15 @@ export function createApprovePlanHandler(autoModeService: AutoModeService) {
|
|||||||
if (!featureId) {
|
if (!featureId) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "featureId is required",
|
error: 'featureId is required',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof approved !== "boolean") {
|
if (typeof approved !== 'boolean') {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "approved must be a boolean",
|
error: 'approved must be a boolean',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,9 @@ export function createApprovePlanHandler(autoModeService: AutoModeService) {
|
|||||||
// This supports cases where the server restarted while waiting for approval
|
// This supports cases where the server restarted while waiting for approval
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`[AutoMode] Plan ${approved ? "approved" : "rejected"} for feature ${featureId}${
|
`[AutoMode] Plan ${approved ? 'approved' : 'rejected'} for feature ${featureId}${
|
||||||
editedPlan ? " (with edits)" : ""
|
editedPlan ? ' (with edits)' : ''
|
||||||
}${feedback ? ` - Feedback: ${feedback}` : ""}`
|
}${feedback ? ` - Feedback: ${feedback}` : ''}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Resolve the pending approval (with recovery support)
|
// Resolve the pending approval (with recovery support)
|
||||||
@@ -67,11 +67,11 @@ export function createApprovePlanHandler(autoModeService: AutoModeService) {
|
|||||||
success: true,
|
success: true,
|
||||||
approved,
|
approved,
|
||||||
message: approved
|
message: approved
|
||||||
? "Plan approved - implementation will continue"
|
? 'Plan approved - implementation will continue'
|
||||||
: "Plan rejected - feature execution stopped",
|
: 'Plan rejected - feature execution stopped',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Approve plan failed");
|
logError(error, 'Approve plan failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* POST /commit-feature endpoint - Commit feature changes
|
* POST /commit-feature endpoint - Commit feature changes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { AutoModeService } from "../../../services/auto-mode-service.js";
|
import type { AutoModeService } from '../../../services/auto-mode-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createCommitFeatureHandler(autoModeService: AutoModeService) {
|
export function createCommitFeatureHandler(autoModeService: AutoModeService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -16,23 +16,17 @@ export function createCommitFeatureHandler(autoModeService: AutoModeService) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!projectPath || !featureId) {
|
if (!projectPath || !featureId) {
|
||||||
res
|
res.status(400).json({
|
||||||
.status(400)
|
success: false,
|
||||||
.json({
|
error: 'projectPath and featureId are required',
|
||||||
success: false,
|
});
|
||||||
error: "projectPath and featureId are required",
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const commitHash = await autoModeService.commitFeature(
|
const commitHash = await autoModeService.commitFeature(projectPath, featureId, worktreePath);
|
||||||
projectPath,
|
|
||||||
featureId,
|
|
||||||
worktreePath
|
|
||||||
);
|
|
||||||
res.json({ success: true, commitHash });
|
res.json({ success: true, commitHash });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Commit feature failed");
|
logError(error, 'Commit feature failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* POST /context-exists endpoint - Check if context exists for a feature
|
* POST /context-exists endpoint - Check if context exists for a feature
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { AutoModeService } from "../../../services/auto-mode-service.js";
|
import type { AutoModeService } from '../../../services/auto-mode-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createContextExistsHandler(autoModeService: AutoModeService) {
|
export function createContextExistsHandler(autoModeService: AutoModeService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -15,22 +15,17 @@ export function createContextExistsHandler(autoModeService: AutoModeService) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!projectPath || !featureId) {
|
if (!projectPath || !featureId) {
|
||||||
res
|
res.status(400).json({
|
||||||
.status(400)
|
success: false,
|
||||||
.json({
|
error: 'projectPath and featureId are required',
|
||||||
success: false,
|
});
|
||||||
error: "projectPath and featureId are required",
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const exists = await autoModeService.contextExists(
|
const exists = await autoModeService.contextExists(projectPath, featureId);
|
||||||
projectPath,
|
|
||||||
featureId
|
|
||||||
);
|
|
||||||
res.json({ success: true, exists });
|
res.json({ success: true, exists });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Check context exists failed");
|
logError(error, 'Check context exists failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,29 +2,28 @@
|
|||||||
* POST /follow-up-feature endpoint - Follow up on a feature
|
* POST /follow-up-feature endpoint - Follow up on a feature
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { AutoModeService } from "../../../services/auto-mode-service.js";
|
import type { AutoModeService } from '../../../services/auto-mode-service.js';
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
const logger = createLogger("AutoMode");
|
const logger = createLogger('AutoMode');
|
||||||
|
|
||||||
export function createFollowUpFeatureHandler(autoModeService: AutoModeService) {
|
export function createFollowUpFeatureHandler(autoModeService: AutoModeService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { projectPath, featureId, prompt, imagePaths, useWorktrees } =
|
const { projectPath, featureId, prompt, imagePaths, useWorktrees } = req.body as {
|
||||||
req.body as {
|
projectPath: string;
|
||||||
projectPath: string;
|
featureId: string;
|
||||||
featureId: string;
|
prompt: string;
|
||||||
prompt: string;
|
imagePaths?: string[];
|
||||||
imagePaths?: string[];
|
useWorktrees?: boolean;
|
||||||
useWorktrees?: boolean;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
if (!projectPath || !featureId || !prompt) {
|
if (!projectPath || !featureId || !prompt) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "projectPath, featureId, and prompt are required",
|
error: 'projectPath, featureId, and prompt are required',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -32,18 +31,9 @@ export function createFollowUpFeatureHandler(autoModeService: AutoModeService) {
|
|||||||
// Start follow-up in background
|
// Start follow-up in background
|
||||||
// followUpFeature derives workDir from feature.branchName
|
// followUpFeature derives workDir from feature.branchName
|
||||||
autoModeService
|
autoModeService
|
||||||
.followUpFeature(
|
.followUpFeature(projectPath, featureId, prompt, imagePaths, useWorktrees ?? true)
|
||||||
projectPath,
|
|
||||||
featureId,
|
|
||||||
prompt,
|
|
||||||
imagePaths,
|
|
||||||
useWorktrees ?? true
|
|
||||||
)
|
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logger.error(
|
logger.error(`[AutoMode] Follow up feature ${featureId} error:`, error);
|
||||||
`[AutoMode] Follow up feature ${featureId} error:`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
// Release the starting slot when follow-up completes (success or error)
|
// Release the starting slot when follow-up completes (success or error)
|
||||||
@@ -52,7 +42,7 @@ export function createFollowUpFeatureHandler(autoModeService: AutoModeService) {
|
|||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Follow up feature failed");
|
logError(error, 'Follow up feature failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
* POST /resume-feature endpoint - Resume a feature
|
* POST /resume-feature endpoint - Resume a feature
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { AutoModeService } from "../../../services/auto-mode-service.js";
|
import type { AutoModeService } from '../../../services/auto-mode-service.js';
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
const logger = createLogger("AutoMode");
|
const logger = createLogger('AutoMode');
|
||||||
|
|
||||||
export function createResumeFeatureHandler(autoModeService: AutoModeService) {
|
export function createResumeFeatureHandler(autoModeService: AutoModeService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -21,7 +21,7 @@ export function createResumeFeatureHandler(autoModeService: AutoModeService) {
|
|||||||
if (!projectPath || !featureId) {
|
if (!projectPath || !featureId) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "projectPath and featureId are required",
|
error: 'projectPath and featureId are required',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ export function createResumeFeatureHandler(autoModeService: AutoModeService) {
|
|||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Resume feature failed");
|
logError(error, 'Resume feature failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
* POST /run-feature endpoint - Run a single feature
|
* POST /run-feature endpoint - Run a single feature
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { AutoModeService } from "../../../services/auto-mode-service.js";
|
import type { AutoModeService } from '../../../services/auto-mode-service.js';
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
const logger = createLogger("AutoMode");
|
const logger = createLogger('AutoMode');
|
||||||
|
|
||||||
export function createRunFeatureHandler(autoModeService: AutoModeService) {
|
export function createRunFeatureHandler(autoModeService: AutoModeService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -21,7 +21,7 @@ export function createRunFeatureHandler(autoModeService: AutoModeService) {
|
|||||||
if (!projectPath || !featureId) {
|
if (!projectPath || !featureId) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "projectPath and featureId are required",
|
error: 'projectPath and featureId are required',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ export function createRunFeatureHandler(autoModeService: AutoModeService) {
|
|||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Run feature failed");
|
logError(error, 'Run feature failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* POST /status endpoint - Get auto mode status
|
* POST /status endpoint - Get auto mode status
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { AutoModeService } from "../../../services/auto-mode-service.js";
|
import type { AutoModeService } from '../../../services/auto-mode-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createStatusHandler(autoModeService: AutoModeService) {
|
export function createStatusHandler(autoModeService: AutoModeService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -15,7 +15,7 @@ export function createStatusHandler(autoModeService: AutoModeService) {
|
|||||||
...status,
|
...status,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Get status failed");
|
logError(error, 'Get status failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* POST /stop-feature endpoint - Stop a specific feature
|
* POST /stop-feature endpoint - Stop a specific feature
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { AutoModeService } from "../../../services/auto-mode-service.js";
|
import type { AutoModeService } from '../../../services/auto-mode-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createStopFeatureHandler(autoModeService: AutoModeService) {
|
export function createStopFeatureHandler(autoModeService: AutoModeService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -12,16 +12,14 @@ export function createStopFeatureHandler(autoModeService: AutoModeService) {
|
|||||||
const { featureId } = req.body as { featureId: string };
|
const { featureId } = req.body as { featureId: string };
|
||||||
|
|
||||||
if (!featureId) {
|
if (!featureId) {
|
||||||
res
|
res.status(400).json({ success: false, error: 'featureId is required' });
|
||||||
.status(400)
|
|
||||||
.json({ success: false, error: "featureId is required" });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopped = await autoModeService.stopFeature(featureId);
|
const stopped = await autoModeService.stopFeature(featureId);
|
||||||
res.json({ success: true, stopped });
|
res.json({ success: true, stopped });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Stop feature failed");
|
logError(error, 'Stop feature failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* POST /verify-feature endpoint - Verify a feature
|
* POST /verify-feature endpoint - Verify a feature
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { AutoModeService } from "../../../services/auto-mode-service.js";
|
import type { AutoModeService } from '../../../services/auto-mode-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createVerifyFeatureHandler(autoModeService: AutoModeService) {
|
export function createVerifyFeatureHandler(autoModeService: AutoModeService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -15,22 +15,17 @@ export function createVerifyFeatureHandler(autoModeService: AutoModeService) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!projectPath || !featureId) {
|
if (!projectPath || !featureId) {
|
||||||
res
|
res.status(400).json({
|
||||||
.status(400)
|
success: false,
|
||||||
.json({
|
error: 'projectPath and featureId are required',
|
||||||
success: false,
|
});
|
||||||
error: "projectPath and featureId are required",
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const passes = await autoModeService.verifyFeature(
|
const passes = await autoModeService.verifyFeature(projectPath, featureId);
|
||||||
projectPath,
|
|
||||||
featureId
|
|
||||||
);
|
|
||||||
res.json({ success: true, passes });
|
res.json({ success: true, passes });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Verify feature failed");
|
logError(error, 'Verify feature failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { Router, Request, Response } from "express";
|
import { Router, Request, Response } from 'express';
|
||||||
import { ClaudeUsageService } from "../../services/claude-usage-service.js";
|
import { ClaudeUsageService } from '../../services/claude-usage-service.js';
|
||||||
|
|
||||||
export function createClaudeRoutes(service: ClaudeUsageService): Router {
|
export function createClaudeRoutes(service: ClaudeUsageService): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
// Get current usage (fetches from Claude CLI)
|
// Get current usage (fetches from Claude CLI)
|
||||||
router.get("/usage", async (req: Request, res: Response) => {
|
router.get('/usage', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
// Check if Claude CLI is available first
|
// Check if Claude CLI is available first
|
||||||
const isAvailable = await service.isAvailable();
|
const isAvailable = await service.isAvailable();
|
||||||
if (!isAvailable) {
|
if (!isAvailable) {
|
||||||
res.status(503).json({
|
res.status(503).json({
|
||||||
error: "Claude CLI not found",
|
error: 'Claude CLI not found',
|
||||||
message: "Please install Claude Code CLI and run 'claude login' to authenticate"
|
message: "Please install Claude Code CLI and run 'claude login' to authenticate",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -20,20 +20,20 @@ export function createClaudeRoutes(service: ClaudeUsageService): Router {
|
|||||||
const usage = await service.fetchUsageData();
|
const usage = await service.fetchUsageData();
|
||||||
res.json(usage);
|
res.json(usage);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : "Unknown error";
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||||
|
|
||||||
if (message.includes("Authentication required") || message.includes("token_expired")) {
|
if (message.includes('Authentication required') || message.includes('token_expired')) {
|
||||||
res.status(401).json({
|
res.status(401).json({
|
||||||
error: "Authentication required",
|
error: 'Authentication required',
|
||||||
message: "Please run 'claude login' to authenticate"
|
message: "Please run 'claude login' to authenticate",
|
||||||
});
|
});
|
||||||
} else if (message.includes("timed out")) {
|
} else if (message.includes('timed out')) {
|
||||||
res.status(504).json({
|
res.status(504).json({
|
||||||
error: "Command timed out",
|
error: 'Command timed out',
|
||||||
message: "The Claude CLI took too long to respond"
|
message: 'The Claude CLI took too long to respond',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error("Error fetching usage:", error);
|
console.error('Error fetching usage:', error);
|
||||||
res.status(500).json({ error: message });
|
res.status(500).json({ error: message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export type ClaudeUsage = {
|
|||||||
|
|
||||||
export type ClaudeStatus = {
|
export type ClaudeStatus = {
|
||||||
indicator: {
|
indicator: {
|
||||||
color: "green" | "yellow" | "orange" | "red" | "gray";
|
color: 'green' | 'yellow' | 'orange' | 'red' | 'gray';
|
||||||
};
|
};
|
||||||
description: string;
|
description: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Common utilities shared across all route modules
|
* Common utilities shared across all route modules
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
|
|
||||||
// Re-export git utilities from shared package
|
// Re-export git utilities from shared package
|
||||||
export {
|
export {
|
||||||
@@ -16,7 +16,7 @@ export {
|
|||||||
listAllFilesInDirectory,
|
listAllFilesInDirectory,
|
||||||
generateDiffsForNonGitDirectory,
|
generateDiffsForNonGitDirectory,
|
||||||
getGitRepositoryDiffs,
|
getGitRepositoryDiffs,
|
||||||
} from "@automaker/git-utils";
|
} from '@automaker/git-utils';
|
||||||
|
|
||||||
type Logger = ReturnType<typeof createLogger>;
|
type Logger = ReturnType<typeof createLogger>;
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ type Logger = ReturnType<typeof createLogger>;
|
|||||||
* Get error message from error object
|
* Get error message from error object
|
||||||
*/
|
*/
|
||||||
export function getErrorMessage(error: unknown): string {
|
export function getErrorMessage(error: unknown): string {
|
||||||
return error instanceof Error ? error.message : "Unknown error";
|
return error instanceof Error ? error.message : 'Unknown error';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
* with different enhancement modes (improve, expand, simplify, etc.)
|
* with different enhancement modes (improve, expand, simplify, etc.)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router } from "express";
|
import { Router } from 'express';
|
||||||
import { createEnhanceHandler } from "./routes/enhance.js";
|
import { createEnhanceHandler } from './routes/enhance.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the enhance-prompt router
|
* Create the enhance-prompt router
|
||||||
@@ -16,7 +16,7 @@ import { createEnhanceHandler } from "./routes/enhance.js";
|
|||||||
export function createEnhancePromptRoutes(): Router {
|
export function createEnhancePromptRoutes(): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post("/", createEnhanceHandler());
|
router.post('/', createEnhanceHandler());
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,19 +5,19 @@
|
|||||||
* Supports modes: improve, technical, simplify, acceptance
|
* Supports modes: improve, technical, simplify, acceptance
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { query } from "@anthropic-ai/claude-agent-sdk";
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import { resolveModelString } from "@automaker/model-resolver";
|
import { resolveModelString } from '@automaker/model-resolver';
|
||||||
import { CLAUDE_MODEL_MAP } from "@automaker/types";
|
import { CLAUDE_MODEL_MAP } from '@automaker/types';
|
||||||
import {
|
import {
|
||||||
getSystemPrompt,
|
getSystemPrompt,
|
||||||
buildUserPrompt,
|
buildUserPrompt,
|
||||||
isValidEnhancementMode,
|
isValidEnhancementMode,
|
||||||
type EnhancementMode,
|
type EnhancementMode,
|
||||||
} from "../../../lib/enhancement-prompts.js";
|
} from '../../../lib/enhancement-prompts.js';
|
||||||
|
|
||||||
const logger = createLogger("EnhancePrompt");
|
const logger = createLogger('EnhancePrompt');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request body for the enhance endpoint
|
* Request body for the enhance endpoint
|
||||||
@@ -63,16 +63,16 @@ async function extractTextFromStream(
|
|||||||
};
|
};
|
||||||
}>
|
}>
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
let responseText = "";
|
let responseText = '';
|
||||||
|
|
||||||
for await (const msg of stream) {
|
for await (const msg of stream) {
|
||||||
if (msg.type === "assistant" && msg.message?.content) {
|
if (msg.type === 'assistant' && msg.message?.content) {
|
||||||
for (const block of msg.message.content) {
|
for (const block of msg.message.content) {
|
||||||
if (block.type === "text" && block.text) {
|
if (block.type === 'text' && block.text) {
|
||||||
responseText += block.text;
|
responseText += block.text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (msg.type === "result" && msg.subtype === "success") {
|
} else if (msg.type === 'result' && msg.subtype === 'success') {
|
||||||
responseText = msg.result || responseText;
|
responseText = msg.result || responseText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,29 +85,25 @@ async function extractTextFromStream(
|
|||||||
*
|
*
|
||||||
* @returns Express request handler for text enhancement
|
* @returns Express request handler for text enhancement
|
||||||
*/
|
*/
|
||||||
export function createEnhanceHandler(): (
|
export function createEnhanceHandler(): (req: Request, res: Response) => Promise<void> {
|
||||||
req: Request,
|
|
||||||
res: Response
|
|
||||||
) => Promise<void> {
|
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { originalText, enhancementMode, model } =
|
const { originalText, enhancementMode, model } = req.body as EnhanceRequestBody;
|
||||||
req.body as EnhanceRequestBody;
|
|
||||||
|
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
if (!originalText || typeof originalText !== "string") {
|
if (!originalText || typeof originalText !== 'string') {
|
||||||
const response: EnhanceErrorResponse = {
|
const response: EnhanceErrorResponse = {
|
||||||
success: false,
|
success: false,
|
||||||
error: "originalText is required and must be a string",
|
error: 'originalText is required and must be a string',
|
||||||
};
|
};
|
||||||
res.status(400).json(response);
|
res.status(400).json(response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!enhancementMode || typeof enhancementMode !== "string") {
|
if (!enhancementMode || typeof enhancementMode !== 'string') {
|
||||||
const response: EnhanceErrorResponse = {
|
const response: EnhanceErrorResponse = {
|
||||||
success: false,
|
success: false,
|
||||||
error: "enhancementMode is required and must be a string",
|
error: 'enhancementMode is required and must be a string',
|
||||||
};
|
};
|
||||||
res.status(400).json(response);
|
res.status(400).json(response);
|
||||||
return;
|
return;
|
||||||
@@ -118,7 +114,7 @@ export function createEnhanceHandler(): (
|
|||||||
if (trimmedText.length === 0) {
|
if (trimmedText.length === 0) {
|
||||||
const response: EnhanceErrorResponse = {
|
const response: EnhanceErrorResponse = {
|
||||||
success: false,
|
success: false,
|
||||||
error: "originalText cannot be empty",
|
error: 'originalText cannot be empty',
|
||||||
};
|
};
|
||||||
res.status(400).json(response);
|
res.status(400).json(response);
|
||||||
return;
|
return;
|
||||||
@@ -128,11 +124,9 @@ export function createEnhanceHandler(): (
|
|||||||
const normalizedMode = enhancementMode.toLowerCase();
|
const normalizedMode = enhancementMode.toLowerCase();
|
||||||
const validMode: EnhancementMode = isValidEnhancementMode(normalizedMode)
|
const validMode: EnhancementMode = isValidEnhancementMode(normalizedMode)
|
||||||
? normalizedMode
|
? normalizedMode
|
||||||
: "improve";
|
: 'improve';
|
||||||
|
|
||||||
logger.info(
|
logger.info(`Enhancing text with mode: ${validMode}, length: ${trimmedText.length} chars`);
|
||||||
`Enhancing text with mode: ${validMode}, length: ${trimmedText.length} chars`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get the system prompt for this mode
|
// Get the system prompt for this mode
|
||||||
const systemPrompt = getSystemPrompt(validMode);
|
const systemPrompt = getSystemPrompt(validMode);
|
||||||
@@ -155,7 +149,7 @@ export function createEnhanceHandler(): (
|
|||||||
systemPrompt,
|
systemPrompt,
|
||||||
maxTurns: 1,
|
maxTurns: 1,
|
||||||
allowedTools: [],
|
allowedTools: [],
|
||||||
permissionMode: "acceptEdits",
|
permissionMode: 'acceptEdits',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -163,18 +157,16 @@ export function createEnhanceHandler(): (
|
|||||||
const enhancedText = await extractTextFromStream(stream);
|
const enhancedText = await extractTextFromStream(stream);
|
||||||
|
|
||||||
if (!enhancedText || enhancedText.trim().length === 0) {
|
if (!enhancedText || enhancedText.trim().length === 0) {
|
||||||
logger.warn("Received empty response from Claude");
|
logger.warn('Received empty response from Claude');
|
||||||
const response: EnhanceErrorResponse = {
|
const response: EnhanceErrorResponse = {
|
||||||
success: false,
|
success: false,
|
||||||
error: "Failed to generate enhanced text - empty response",
|
error: 'Failed to generate enhanced text - empty response',
|
||||||
};
|
};
|
||||||
res.status(500).json(response);
|
res.status(500).json(response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
logger.info(`Enhancement complete, output length: ${enhancedText.length} chars`);
|
||||||
`Enhancement complete, output length: ${enhancedText.length} chars`
|
|
||||||
);
|
|
||||||
|
|
||||||
const response: EnhanceSuccessResponse = {
|
const response: EnhanceSuccessResponse = {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -182,9 +174,8 @@ export function createEnhanceHandler(): (
|
|||||||
};
|
};
|
||||||
res.json(response);
|
res.json(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage =
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
||||||
error instanceof Error ? error.message : "Unknown error occurred";
|
logger.error('Enhancement failed:', errorMessage);
|
||||||
logger.error("Enhancement failed:", errorMessage);
|
|
||||||
|
|
||||||
const response: EnhanceErrorResponse = {
|
const response: EnhanceErrorResponse = {
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
@@ -2,13 +2,10 @@
|
|||||||
* Common utilities for features routes
|
* Common utilities for features routes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import {
|
import { getErrorMessage as getErrorMessageShared, createLogError } from '../common.js';
|
||||||
getErrorMessage as getErrorMessageShared,
|
|
||||||
createLogError,
|
|
||||||
} from "../common.js";
|
|
||||||
|
|
||||||
const logger = createLogger("Features");
|
const logger = createLogger('Features');
|
||||||
|
|
||||||
// Re-export shared utilities
|
// Re-export shared utilities
|
||||||
export { getErrorMessageShared as getErrorMessage };
|
export { getErrorMessageShared as getErrorMessage };
|
||||||
|
|||||||
@@ -2,27 +2,27 @@
|
|||||||
* Features routes - HTTP API for feature management
|
* Features routes - HTTP API for feature management
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router } from "express";
|
import { Router } from 'express';
|
||||||
import { FeatureLoader } from "../../services/feature-loader.js";
|
import { FeatureLoader } from '../../services/feature-loader.js';
|
||||||
import { validatePathParams } from "../../middleware/validate-paths.js";
|
import { validatePathParams } from '../../middleware/validate-paths.js';
|
||||||
import { createListHandler } from "./routes/list.js";
|
import { createListHandler } from './routes/list.js';
|
||||||
import { createGetHandler } from "./routes/get.js";
|
import { createGetHandler } from './routes/get.js';
|
||||||
import { createCreateHandler } from "./routes/create.js";
|
import { createCreateHandler } from './routes/create.js';
|
||||||
import { createUpdateHandler } from "./routes/update.js";
|
import { createUpdateHandler } from './routes/update.js';
|
||||||
import { createDeleteHandler } from "./routes/delete.js";
|
import { createDeleteHandler } from './routes/delete.js';
|
||||||
import { createAgentOutputHandler } from "./routes/agent-output.js";
|
import { createAgentOutputHandler } from './routes/agent-output.js';
|
||||||
import { createGenerateTitleHandler } from "./routes/generate-title.js";
|
import { createGenerateTitleHandler } from './routes/generate-title.js';
|
||||||
|
|
||||||
export function createFeaturesRoutes(featureLoader: FeatureLoader): Router {
|
export function createFeaturesRoutes(featureLoader: FeatureLoader): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post("/list", validatePathParams("projectPath"), createListHandler(featureLoader));
|
router.post('/list', validatePathParams('projectPath'), createListHandler(featureLoader));
|
||||||
router.post("/get", validatePathParams("projectPath"), createGetHandler(featureLoader));
|
router.post('/get', validatePathParams('projectPath'), createGetHandler(featureLoader));
|
||||||
router.post("/create", validatePathParams("projectPath"), createCreateHandler(featureLoader));
|
router.post('/create', validatePathParams('projectPath'), createCreateHandler(featureLoader));
|
||||||
router.post("/update", validatePathParams("projectPath"), createUpdateHandler(featureLoader));
|
router.post('/update', validatePathParams('projectPath'), createUpdateHandler(featureLoader));
|
||||||
router.post("/delete", validatePathParams("projectPath"), createDeleteHandler(featureLoader));
|
router.post('/delete', validatePathParams('projectPath'), createDeleteHandler(featureLoader));
|
||||||
router.post("/agent-output", createAgentOutputHandler(featureLoader));
|
router.post('/agent-output', createAgentOutputHandler(featureLoader));
|
||||||
router.post("/generate-title", createGenerateTitleHandler());
|
router.post('/generate-title', createGenerateTitleHandler());
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* POST /agent-output endpoint - Get agent output for a feature
|
* POST /agent-output endpoint - Get agent output for a feature
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { FeatureLoader } from "../../../services/feature-loader.js";
|
import { FeatureLoader } from '../../../services/feature-loader.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createAgentOutputHandler(featureLoader: FeatureLoader) {
|
export function createAgentOutputHandler(featureLoader: FeatureLoader) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -15,22 +15,17 @@ export function createAgentOutputHandler(featureLoader: FeatureLoader) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!projectPath || !featureId) {
|
if (!projectPath || !featureId) {
|
||||||
res
|
res.status(400).json({
|
||||||
.status(400)
|
success: false,
|
||||||
.json({
|
error: 'projectPath and featureId are required',
|
||||||
success: false,
|
});
|
||||||
error: "projectPath and featureId are required",
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = await featureLoader.getAgentOutput(
|
const content = await featureLoader.getAgentOutput(projectPath, featureId);
|
||||||
projectPath,
|
|
||||||
featureId
|
|
||||||
);
|
|
||||||
res.json({ success: true, content });
|
res.json({ success: true, content });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Get agent output failed");
|
logError(error, 'Get agent output failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
* POST /create endpoint - Create a new feature
|
* POST /create endpoint - Create a new feature
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { FeatureLoader } from "../../../services/feature-loader.js";
|
import { FeatureLoader } from '../../../services/feature-loader.js';
|
||||||
import type { Feature } from "@automaker/types";
|
import type { Feature } from '@automaker/types';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createCreateHandler(featureLoader: FeatureLoader) {
|
export function createCreateHandler(featureLoader: FeatureLoader) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -18,7 +18,7 @@ export function createCreateHandler(featureLoader: FeatureLoader) {
|
|||||||
if (!projectPath || !feature) {
|
if (!projectPath || !feature) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "projectPath and feature are required",
|
error: 'projectPath and feature are required',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ export function createCreateHandler(featureLoader: FeatureLoader) {
|
|||||||
const created = await featureLoader.create(projectPath, feature);
|
const created = await featureLoader.create(projectPath, feature);
|
||||||
res.json({ success: true, feature: created });
|
res.json({ success: true, feature: created });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Create feature failed");
|
logError(error, 'Create feature failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* POST /delete endpoint - Delete a feature
|
* POST /delete endpoint - Delete a feature
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { FeatureLoader } from "../../../services/feature-loader.js";
|
import { FeatureLoader } from '../../../services/feature-loader.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createDeleteHandler(featureLoader: FeatureLoader) {
|
export function createDeleteHandler(featureLoader: FeatureLoader) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -15,19 +15,17 @@ export function createDeleteHandler(featureLoader: FeatureLoader) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!projectPath || !featureId) {
|
if (!projectPath || !featureId) {
|
||||||
res
|
res.status(400).json({
|
||||||
.status(400)
|
success: false,
|
||||||
.json({
|
error: 'projectPath and featureId are required',
|
||||||
success: false,
|
});
|
||||||
error: "projectPath and featureId are required",
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const success = await featureLoader.delete(projectPath, featureId);
|
const success = await featureLoader.delete(projectPath, featureId);
|
||||||
res.json({ success });
|
res.json({ success });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Delete feature failed");
|
logError(error, 'Delete feature failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
* Uses Claude Haiku to generate a short, descriptive title from feature description.
|
* Uses Claude Haiku to generate a short, descriptive title from feature description.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { query } from "@anthropic-ai/claude-agent-sdk";
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import { CLAUDE_MODEL_MAP } from "@automaker/model-resolver";
|
import { CLAUDE_MODEL_MAP } from '@automaker/model-resolver';
|
||||||
|
|
||||||
const logger = createLogger("GenerateTitle");
|
const logger = createLogger('GenerateTitle');
|
||||||
|
|
||||||
interface GenerateTitleRequestBody {
|
interface GenerateTitleRequestBody {
|
||||||
description: string;
|
description: string;
|
||||||
@@ -44,16 +44,16 @@ async function extractTextFromStream(
|
|||||||
};
|
};
|
||||||
}>
|
}>
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
let responseText = "";
|
let responseText = '';
|
||||||
|
|
||||||
for await (const msg of stream) {
|
for await (const msg of stream) {
|
||||||
if (msg.type === "assistant" && msg.message?.content) {
|
if (msg.type === 'assistant' && msg.message?.content) {
|
||||||
for (const block of msg.message.content) {
|
for (const block of msg.message.content) {
|
||||||
if (block.type === "text" && block.text) {
|
if (block.type === 'text' && block.text) {
|
||||||
responseText += block.text;
|
responseText += block.text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (msg.type === "result" && msg.subtype === "success") {
|
} else if (msg.type === 'result' && msg.subtype === 'success') {
|
||||||
responseText = msg.result || responseText;
|
responseText = msg.result || responseText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,18 +61,15 @@ async function extractTextFromStream(
|
|||||||
return responseText;
|
return responseText;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createGenerateTitleHandler(): (
|
export function createGenerateTitleHandler(): (req: Request, res: Response) => Promise<void> {
|
||||||
req: Request,
|
|
||||||
res: Response
|
|
||||||
) => Promise<void> {
|
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { description } = req.body as GenerateTitleRequestBody;
|
const { description } = req.body as GenerateTitleRequestBody;
|
||||||
|
|
||||||
if (!description || typeof description !== "string") {
|
if (!description || typeof description !== 'string') {
|
||||||
const response: GenerateTitleErrorResponse = {
|
const response: GenerateTitleErrorResponse = {
|
||||||
success: false,
|
success: false,
|
||||||
error: "description is required and must be a string",
|
error: 'description is required and must be a string',
|
||||||
};
|
};
|
||||||
res.status(400).json(response);
|
res.status(400).json(response);
|
||||||
return;
|
return;
|
||||||
@@ -82,7 +79,7 @@ export function createGenerateTitleHandler(): (
|
|||||||
if (trimmedDescription.length === 0) {
|
if (trimmedDescription.length === 0) {
|
||||||
const response: GenerateTitleErrorResponse = {
|
const response: GenerateTitleErrorResponse = {
|
||||||
success: false,
|
success: false,
|
||||||
error: "description cannot be empty",
|
error: 'description cannot be empty',
|
||||||
};
|
};
|
||||||
res.status(400).json(response);
|
res.status(400).json(response);
|
||||||
return;
|
return;
|
||||||
@@ -99,17 +96,17 @@ export function createGenerateTitleHandler(): (
|
|||||||
systemPrompt: SYSTEM_PROMPT,
|
systemPrompt: SYSTEM_PROMPT,
|
||||||
maxTurns: 1,
|
maxTurns: 1,
|
||||||
allowedTools: [],
|
allowedTools: [],
|
||||||
permissionMode: "acceptEdits",
|
permissionMode: 'acceptEdits',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const title = await extractTextFromStream(stream);
|
const title = await extractTextFromStream(stream);
|
||||||
|
|
||||||
if (!title || title.trim().length === 0) {
|
if (!title || title.trim().length === 0) {
|
||||||
logger.warn("Received empty response from Claude");
|
logger.warn('Received empty response from Claude');
|
||||||
const response: GenerateTitleErrorResponse = {
|
const response: GenerateTitleErrorResponse = {
|
||||||
success: false,
|
success: false,
|
||||||
error: "Failed to generate title - empty response",
|
error: 'Failed to generate title - empty response',
|
||||||
};
|
};
|
||||||
res.status(500).json(response);
|
res.status(500).json(response);
|
||||||
return;
|
return;
|
||||||
@@ -123,9 +120,8 @@ export function createGenerateTitleHandler(): (
|
|||||||
};
|
};
|
||||||
res.json(response);
|
res.json(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage =
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
||||||
error instanceof Error ? error.message : "Unknown error occurred";
|
logger.error('Title generation failed:', errorMessage);
|
||||||
logger.error("Title generation failed:", errorMessage);
|
|
||||||
|
|
||||||
const response: GenerateTitleErrorResponse = {
|
const response: GenerateTitleErrorResponse = {
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* POST /get endpoint - Get a single feature
|
* POST /get endpoint - Get a single feature
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { FeatureLoader } from "../../../services/feature-loader.js";
|
import { FeatureLoader } from '../../../services/feature-loader.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createGetHandler(featureLoader: FeatureLoader) {
|
export function createGetHandler(featureLoader: FeatureLoader) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -15,24 +15,22 @@ export function createGetHandler(featureLoader: FeatureLoader) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!projectPath || !featureId) {
|
if (!projectPath || !featureId) {
|
||||||
res
|
res.status(400).json({
|
||||||
.status(400)
|
success: false,
|
||||||
.json({
|
error: 'projectPath and featureId are required',
|
||||||
success: false,
|
});
|
||||||
error: "projectPath and featureId are required",
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const feature = await featureLoader.get(projectPath, featureId);
|
const feature = await featureLoader.get(projectPath, featureId);
|
||||||
if (!feature) {
|
if (!feature) {
|
||||||
res.status(404).json({ success: false, error: "Feature not found" });
|
res.status(404).json({ success: false, error: 'Feature not found' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({ success: true, feature });
|
res.json({ success: true, feature });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Get feature failed");
|
logError(error, 'Get feature failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* POST /list endpoint - List all features for a project
|
* POST /list endpoint - List all features for a project
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { FeatureLoader } from "../../../services/feature-loader.js";
|
import { FeatureLoader } from '../../../services/feature-loader.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createListHandler(featureLoader: FeatureLoader) {
|
export function createListHandler(featureLoader: FeatureLoader) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -12,16 +12,14 @@ export function createListHandler(featureLoader: FeatureLoader) {
|
|||||||
const { projectPath } = req.body as { projectPath: string };
|
const { projectPath } = req.body as { projectPath: string };
|
||||||
|
|
||||||
if (!projectPath) {
|
if (!projectPath) {
|
||||||
res
|
res.status(400).json({ success: false, error: 'projectPath is required' });
|
||||||
.status(400)
|
|
||||||
.json({ success: false, error: "projectPath is required" });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const features = await featureLoader.getAll(projectPath);
|
const features = await featureLoader.getAll(projectPath);
|
||||||
res.json({ success: true, features });
|
res.json({ success: true, features });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "List features failed");
|
logError(error, 'List features failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
* POST /update endpoint - Update a feature
|
* POST /update endpoint - Update a feature
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { FeatureLoader } from "../../../services/feature-loader.js";
|
import { FeatureLoader } from '../../../services/feature-loader.js';
|
||||||
import type { Feature } from "@automaker/types";
|
import type { Feature } from '@automaker/types';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createUpdateHandler(featureLoader: FeatureLoader) {
|
export function createUpdateHandler(featureLoader: FeatureLoader) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -19,19 +19,15 @@ export function createUpdateHandler(featureLoader: FeatureLoader) {
|
|||||||
if (!projectPath || !featureId || !updates) {
|
if (!projectPath || !featureId || !updates) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "projectPath, featureId, and updates are required",
|
error: 'projectPath, featureId, and updates are required',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updated = await featureLoader.update(
|
const updated = await featureLoader.update(projectPath, featureId, updates);
|
||||||
projectPath,
|
|
||||||
featureId,
|
|
||||||
updates
|
|
||||||
);
|
|
||||||
res.json({ success: true, feature: updated });
|
res.json({ success: true, feature: updated });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Update feature failed");
|
logError(error, 'Update feature failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,13 +2,10 @@
|
|||||||
* Common utilities for fs routes
|
* Common utilities for fs routes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import {
|
import { getErrorMessage as getErrorMessageShared, createLogError } from '../common.js';
|
||||||
getErrorMessage as getErrorMessageShared,
|
|
||||||
createLogError,
|
|
||||||
} from "../common.js";
|
|
||||||
|
|
||||||
const logger = createLogger("FS");
|
const logger = createLogger('FS');
|
||||||
|
|
||||||
// Re-export shared utilities
|
// Re-export shared utilities
|
||||||
export { getErrorMessageShared as getErrorMessage };
|
export { getErrorMessageShared as getErrorMessage };
|
||||||
|
|||||||
@@ -3,40 +3,40 @@
|
|||||||
* Provides REST API equivalents for Electron IPC file operations
|
* Provides REST API equivalents for Electron IPC file operations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router } from "express";
|
import { Router } from 'express';
|
||||||
import type { EventEmitter } from "../../lib/events.js";
|
import type { EventEmitter } from '../../lib/events.js';
|
||||||
import { createReadHandler } from "./routes/read.js";
|
import { createReadHandler } from './routes/read.js';
|
||||||
import { createWriteHandler } from "./routes/write.js";
|
import { createWriteHandler } from './routes/write.js';
|
||||||
import { createMkdirHandler } from "./routes/mkdir.js";
|
import { createMkdirHandler } from './routes/mkdir.js';
|
||||||
import { createReaddirHandler } from "./routes/readdir.js";
|
import { createReaddirHandler } from './routes/readdir.js';
|
||||||
import { createExistsHandler } from "./routes/exists.js";
|
import { createExistsHandler } from './routes/exists.js';
|
||||||
import { createStatHandler } from "./routes/stat.js";
|
import { createStatHandler } from './routes/stat.js';
|
||||||
import { createDeleteHandler } from "./routes/delete.js";
|
import { createDeleteHandler } from './routes/delete.js';
|
||||||
import { createValidatePathHandler } from "./routes/validate-path.js";
|
import { createValidatePathHandler } from './routes/validate-path.js';
|
||||||
import { createResolveDirectoryHandler } from "./routes/resolve-directory.js";
|
import { createResolveDirectoryHandler } from './routes/resolve-directory.js';
|
||||||
import { createSaveImageHandler } from "./routes/save-image.js";
|
import { createSaveImageHandler } from './routes/save-image.js';
|
||||||
import { createBrowseHandler } from "./routes/browse.js";
|
import { createBrowseHandler } from './routes/browse.js';
|
||||||
import { createImageHandler } from "./routes/image.js";
|
import { createImageHandler } from './routes/image.js';
|
||||||
import { createSaveBoardBackgroundHandler } from "./routes/save-board-background.js";
|
import { createSaveBoardBackgroundHandler } from './routes/save-board-background.js';
|
||||||
import { createDeleteBoardBackgroundHandler } from "./routes/delete-board-background.js";
|
import { createDeleteBoardBackgroundHandler } from './routes/delete-board-background.js';
|
||||||
|
|
||||||
export function createFsRoutes(_events: EventEmitter): Router {
|
export function createFsRoutes(_events: EventEmitter): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post("/read", createReadHandler());
|
router.post('/read', createReadHandler());
|
||||||
router.post("/write", createWriteHandler());
|
router.post('/write', createWriteHandler());
|
||||||
router.post("/mkdir", createMkdirHandler());
|
router.post('/mkdir', createMkdirHandler());
|
||||||
router.post("/readdir", createReaddirHandler());
|
router.post('/readdir', createReaddirHandler());
|
||||||
router.post("/exists", createExistsHandler());
|
router.post('/exists', createExistsHandler());
|
||||||
router.post("/stat", createStatHandler());
|
router.post('/stat', createStatHandler());
|
||||||
router.post("/delete", createDeleteHandler());
|
router.post('/delete', createDeleteHandler());
|
||||||
router.post("/validate-path", createValidatePathHandler());
|
router.post('/validate-path', createValidatePathHandler());
|
||||||
router.post("/resolve-directory", createResolveDirectoryHandler());
|
router.post('/resolve-directory', createResolveDirectoryHandler());
|
||||||
router.post("/save-image", createSaveImageHandler());
|
router.post('/save-image', createSaveImageHandler());
|
||||||
router.post("/browse", createBrowseHandler());
|
router.post('/browse', createBrowseHandler());
|
||||||
router.get("/image", createImageHandler());
|
router.get('/image', createImageHandler());
|
||||||
router.post("/save-board-background", createSaveBoardBackgroundHandler());
|
router.post('/save-board-background', createSaveBoardBackgroundHandler());
|
||||||
router.post("/delete-board-background", createDeleteBoardBackgroundHandler());
|
router.post('/delete-board-background', createDeleteBoardBackgroundHandler());
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,10 @@
|
|||||||
* Common utilities for git routes
|
* Common utilities for git routes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import {
|
import { getErrorMessage as getErrorMessageShared, createLogError } from '../common.js';
|
||||||
getErrorMessage as getErrorMessageShared,
|
|
||||||
createLogError,
|
|
||||||
} from "../common.js";
|
|
||||||
|
|
||||||
const logger = createLogger("Git");
|
const logger = createLogger('Git');
|
||||||
|
|
||||||
// Re-export shared utilities
|
// Re-export shared utilities
|
||||||
export { getErrorMessageShared as getErrorMessage };
|
export { getErrorMessageShared as getErrorMessage };
|
||||||
|
|||||||
@@ -2,16 +2,16 @@
|
|||||||
* Git routes - HTTP API for git operations (non-worktree)
|
* Git routes - HTTP API for git operations (non-worktree)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router } from "express";
|
import { Router } from 'express';
|
||||||
import { validatePathParams } from "../../middleware/validate-paths.js";
|
import { validatePathParams } from '../../middleware/validate-paths.js';
|
||||||
import { createDiffsHandler } from "./routes/diffs.js";
|
import { createDiffsHandler } from './routes/diffs.js';
|
||||||
import { createFileDiffHandler } from "./routes/file-diff.js";
|
import { createFileDiffHandler } from './routes/file-diff.js';
|
||||||
|
|
||||||
export function createGitRoutes(): Router {
|
export function createGitRoutes(): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post("/diffs", validatePathParams("projectPath"), createDiffsHandler());
|
router.post('/diffs', validatePathParams('projectPath'), createDiffsHandler());
|
||||||
router.post("/file-diff", validatePathParams("projectPath", "filePath"), createFileDiffHandler());
|
router.post('/file-diff', validatePathParams('projectPath', 'filePath'), createFileDiffHandler());
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* POST /diffs endpoint - Get diffs for the main project
|
* POST /diffs endpoint - Get diffs for the main project
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
import { getGitRepositoryDiffs } from "../../common.js";
|
import { getGitRepositoryDiffs } from '../../common.js';
|
||||||
|
|
||||||
export function createDiffsHandler() {
|
export function createDiffsHandler() {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -12,7 +12,7 @@ export function createDiffsHandler() {
|
|||||||
const { projectPath } = req.body as { projectPath: string };
|
const { projectPath } = req.body as { projectPath: string };
|
||||||
|
|
||||||
if (!projectPath) {
|
if (!projectPath) {
|
||||||
res.status(400).json({ success: false, error: "projectPath required" });
|
res.status(400).json({ success: false, error: 'projectPath required' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,11 +25,11 @@ export function createDiffsHandler() {
|
|||||||
hasChanges: result.hasChanges,
|
hasChanges: result.hasChanges,
|
||||||
});
|
});
|
||||||
} catch (innerError) {
|
} catch (innerError) {
|
||||||
logError(innerError, "Git diff failed");
|
logError(innerError, 'Git diff failed');
|
||||||
res.json({ success: true, diff: "", files: [], hasChanges: false });
|
res.json({ success: true, diff: '', files: [], hasChanges: false });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Get diffs failed");
|
logError(error, 'Get diffs failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
* POST /file-diff endpoint - Get diff for a specific file
|
* POST /file-diff endpoint - Get diff for a specific file
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { exec } from "child_process";
|
import { exec } from 'child_process';
|
||||||
import { promisify } from "util";
|
import { promisify } from 'util';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
import { generateSyntheticDiffForNewFile } from "../../common.js";
|
import { generateSyntheticDiffForNewFile } from '../../common.js';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
@@ -19,20 +19,17 @@ export function createFileDiffHandler() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!projectPath || !filePath) {
|
if (!projectPath || !filePath) {
|
||||||
res
|
res.status(400).json({ success: false, error: 'projectPath and filePath required' });
|
||||||
.status(400)
|
|
||||||
.json({ success: false, error: "projectPath and filePath required" });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// First check if the file is untracked
|
// First check if the file is untracked
|
||||||
const { stdout: status } = await execAsync(
|
const { stdout: status } = await execAsync(`git status --porcelain -- "${filePath}"`, {
|
||||||
`git status --porcelain -- "${filePath}"`,
|
cwd: projectPath,
|
||||||
{ cwd: projectPath }
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const isUntracked = status.trim().startsWith("??");
|
const isUntracked = status.trim().startsWith('??');
|
||||||
|
|
||||||
let diff: string;
|
let diff: string;
|
||||||
if (isUntracked) {
|
if (isUntracked) {
|
||||||
@@ -40,23 +37,20 @@ export function createFileDiffHandler() {
|
|||||||
diff = await generateSyntheticDiffForNewFile(projectPath, filePath);
|
diff = await generateSyntheticDiffForNewFile(projectPath, filePath);
|
||||||
} else {
|
} else {
|
||||||
// Use regular git diff for tracked files
|
// Use regular git diff for tracked files
|
||||||
const result = await execAsync(
|
const result = await execAsync(`git diff HEAD -- "${filePath}"`, {
|
||||||
`git diff HEAD -- "${filePath}"`,
|
cwd: projectPath,
|
||||||
{
|
maxBuffer: 10 * 1024 * 1024,
|
||||||
cwd: projectPath,
|
});
|
||||||
maxBuffer: 10 * 1024 * 1024,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
diff = result.stdout;
|
diff = result.stdout;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({ success: true, diff, filePath });
|
res.json({ success: true, diff, filePath });
|
||||||
} catch (innerError) {
|
} catch (innerError) {
|
||||||
logError(innerError, "Git file diff failed");
|
logError(innerError, 'Git file diff failed');
|
||||||
res.json({ success: true, diff: "", filePath });
|
res.json({ success: true, diff: '', filePath });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Get file diff failed");
|
logError(error, 'Get file diff failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,13 +2,10 @@
|
|||||||
* Common utilities for health routes
|
* Common utilities for health routes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import {
|
import { getErrorMessage as getErrorMessageShared, createLogError } from '../common.js';
|
||||||
getErrorMessage as getErrorMessageShared,
|
|
||||||
createLogError,
|
|
||||||
} from "../common.js";
|
|
||||||
|
|
||||||
const logger = createLogger("Health");
|
const logger = createLogger('Health');
|
||||||
|
|
||||||
// Re-export shared utilities
|
// Re-export shared utilities
|
||||||
export { getErrorMessageShared as getErrorMessage };
|
export { getErrorMessageShared as getErrorMessage };
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
* Health check routes
|
* Health check routes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router } from "express";
|
import { Router } from 'express';
|
||||||
import { createIndexHandler } from "./routes/index.js";
|
import { createIndexHandler } from './routes/index.js';
|
||||||
import { createDetailedHandler } from "./routes/detailed.js";
|
import { createDetailedHandler } from './routes/detailed.js';
|
||||||
|
|
||||||
export function createHealthRoutes(): Router {
|
export function createHealthRoutes(): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", createIndexHandler());
|
router.get('/', createIndexHandler());
|
||||||
router.get("/detailed", createDetailedHandler());
|
router.get('/detailed', createDetailedHandler());
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
* GET /detailed endpoint - Detailed health check
|
* GET /detailed endpoint - Detailed health check
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { getAuthStatus } from "../../../lib/auth.js";
|
import { getAuthStatus } from '../../../lib/auth.js';
|
||||||
|
|
||||||
export function createDetailedHandler() {
|
export function createDetailedHandler() {
|
||||||
return (_req: Request, res: Response): void => {
|
return (_req: Request, res: Response): void => {
|
||||||
res.json({
|
res.json({
|
||||||
status: "ok",
|
status: 'ok',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
version: process.env.npm_package_version || "0.1.0",
|
version: process.env.npm_package_version || '0.1.0',
|
||||||
uptime: process.uptime(),
|
uptime: process.uptime(),
|
||||||
memory: process.memoryUsage(),
|
memory: process.memoryUsage(),
|
||||||
dataDir: process.env.DATA_DIR || "./data",
|
dataDir: process.env.DATA_DIR || './data',
|
||||||
auth: getAuthStatus(),
|
auth: getAuthStatus(),
|
||||||
env: {
|
env: {
|
||||||
nodeVersion: process.version,
|
nodeVersion: process.version,
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
* GET / endpoint - Basic health check
|
* GET / endpoint - Basic health check
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
|
|
||||||
export function createIndexHandler() {
|
export function createIndexHandler() {
|
||||||
return (_req: Request, res: Response): void => {
|
return (_req: Request, res: Response): void => {
|
||||||
res.json({
|
res.json({
|
||||||
status: "ok",
|
status: 'ok',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
version: process.env.npm_package_version || "0.1.0",
|
version: process.env.npm_package_version || '0.1.0',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,10 @@
|
|||||||
* Common utilities for models routes
|
* Common utilities for models routes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import {
|
import { getErrorMessage as getErrorMessageShared, createLogError } from '../common.js';
|
||||||
getErrorMessage as getErrorMessageShared,
|
|
||||||
createLogError,
|
|
||||||
} from "../common.js";
|
|
||||||
|
|
||||||
const logger = createLogger("Models");
|
const logger = createLogger('Models');
|
||||||
|
|
||||||
// Re-export shared utilities
|
// Re-export shared utilities
|
||||||
export { getErrorMessageShared as getErrorMessage };
|
export { getErrorMessageShared as getErrorMessage };
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
* Models routes - HTTP API for model providers and availability
|
* Models routes - HTTP API for model providers and availability
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router } from "express";
|
import { Router } from 'express';
|
||||||
import { createAvailableHandler } from "./routes/available.js";
|
import { createAvailableHandler } from './routes/available.js';
|
||||||
import { createProvidersHandler } from "./routes/providers.js";
|
import { createProvidersHandler } from './routes/providers.js';
|
||||||
|
|
||||||
export function createModelsRoutes(): Router {
|
export function createModelsRoutes(): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/available", createAvailableHandler());
|
router.get('/available', createAvailableHandler());
|
||||||
router.get("/providers", createProvidersHandler());
|
router.get('/providers', createProvidersHandler());
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
* GET /available endpoint - Get available models
|
* GET /available endpoint - Get available models
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
interface ModelDefinition {
|
interface ModelDefinition {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -20,36 +20,36 @@ export function createAvailableHandler() {
|
|||||||
try {
|
try {
|
||||||
const models: ModelDefinition[] = [
|
const models: ModelDefinition[] = [
|
||||||
{
|
{
|
||||||
id: "claude-opus-4-5-20251101",
|
id: 'claude-opus-4-5-20251101',
|
||||||
name: "Claude Opus 4.5",
|
name: 'Claude Opus 4.5',
|
||||||
provider: "anthropic",
|
provider: 'anthropic',
|
||||||
contextWindow: 200000,
|
contextWindow: 200000,
|
||||||
maxOutputTokens: 16384,
|
maxOutputTokens: 16384,
|
||||||
supportsVision: true,
|
supportsVision: true,
|
||||||
supportsTools: true,
|
supportsTools: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "claude-sonnet-4-20250514",
|
id: 'claude-sonnet-4-20250514',
|
||||||
name: "Claude Sonnet 4",
|
name: 'Claude Sonnet 4',
|
||||||
provider: "anthropic",
|
provider: 'anthropic',
|
||||||
contextWindow: 200000,
|
contextWindow: 200000,
|
||||||
maxOutputTokens: 16384,
|
maxOutputTokens: 16384,
|
||||||
supportsVision: true,
|
supportsVision: true,
|
||||||
supportsTools: true,
|
supportsTools: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "claude-3-5-sonnet-20241022",
|
id: 'claude-3-5-sonnet-20241022',
|
||||||
name: "Claude 3.5 Sonnet",
|
name: 'Claude 3.5 Sonnet',
|
||||||
provider: "anthropic",
|
provider: 'anthropic',
|
||||||
contextWindow: 200000,
|
contextWindow: 200000,
|
||||||
maxOutputTokens: 8192,
|
maxOutputTokens: 8192,
|
||||||
supportsVision: true,
|
supportsVision: true,
|
||||||
supportsTools: true,
|
supportsTools: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "claude-3-5-haiku-20241022",
|
id: 'claude-3-5-haiku-20241022',
|
||||||
name: "Claude 3.5 Haiku",
|
name: 'Claude 3.5 Haiku',
|
||||||
provider: "anthropic",
|
provider: 'anthropic',
|
||||||
contextWindow: 200000,
|
contextWindow: 200000,
|
||||||
maxOutputTokens: 8192,
|
maxOutputTokens: 8192,
|
||||||
supportsVision: true,
|
supportsVision: true,
|
||||||
@@ -59,7 +59,7 @@ export function createAvailableHandler() {
|
|||||||
|
|
||||||
res.json({ success: true, models });
|
res.json({ success: true, models });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Get available models failed");
|
logError(error, 'Get available models failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* GET /providers endpoint - Check provider status
|
* GET /providers endpoint - Check provider status
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { ProviderFactory } from "../../../providers/provider-factory.js";
|
import { ProviderFactory } from '../../../providers/provider-factory.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createProvidersHandler() {
|
export function createProvidersHandler() {
|
||||||
return async (_req: Request, res: Response): Promise<void> => {
|
return async (_req: Request, res: Response): Promise<void> => {
|
||||||
@@ -21,7 +21,7 @@ export function createProvidersHandler() {
|
|||||||
|
|
||||||
res.json({ success: true, providers });
|
res.json({ success: true, providers });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Get providers failed");
|
logError(error, 'Get providers failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,13 +2,10 @@
|
|||||||
* Common utilities for running-agents routes
|
* Common utilities for running-agents routes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import {
|
import { getErrorMessage as getErrorMessageShared, createLogError } from '../common.js';
|
||||||
getErrorMessage as getErrorMessageShared,
|
|
||||||
createLogError,
|
|
||||||
} from "../common.js";
|
|
||||||
|
|
||||||
const logger = createLogger("RunningAgents");
|
const logger = createLogger('RunningAgents');
|
||||||
|
|
||||||
// Re-export shared utilities
|
// Re-export shared utilities
|
||||||
export { getErrorMessageShared as getErrorMessage };
|
export { getErrorMessageShared as getErrorMessage };
|
||||||
|
|||||||
@@ -2,16 +2,14 @@
|
|||||||
* Running Agents routes - HTTP API for tracking active agent executions
|
* Running Agents routes - HTTP API for tracking active agent executions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router } from "express";
|
import { Router } from 'express';
|
||||||
import type { AutoModeService } from "../../services/auto-mode-service.js";
|
import type { AutoModeService } from '../../services/auto-mode-service.js';
|
||||||
import { createIndexHandler } from "./routes/index.js";
|
import { createIndexHandler } from './routes/index.js';
|
||||||
|
|
||||||
export function createRunningAgentsRoutes(
|
export function createRunningAgentsRoutes(autoModeService: AutoModeService): Router {
|
||||||
autoModeService: AutoModeService
|
|
||||||
): Router {
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", createIndexHandler(autoModeService));
|
router.get('/', createIndexHandler(autoModeService));
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* GET / endpoint - Get all running agents
|
* GET / endpoint - Get all running agents
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { AutoModeService } from "../../../services/auto-mode-service.js";
|
import type { AutoModeService } from '../../../services/auto-mode-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createIndexHandler(autoModeService: AutoModeService) {
|
export function createIndexHandler(autoModeService: AutoModeService) {
|
||||||
return async (_req: Request, res: Response): Promise<void> => {
|
return async (_req: Request, res: Response): Promise<void> => {
|
||||||
@@ -18,7 +18,7 @@ export function createIndexHandler(autoModeService: AutoModeService) {
|
|||||||
totalCount: runningAgents.length,
|
totalCount: runningAgents.length,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Get running agents failed");
|
logError(error, 'Get running agents failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,13 +2,10 @@
|
|||||||
* Common utilities for sessions routes
|
* Common utilities for sessions routes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import {
|
import { getErrorMessage as getErrorMessageShared, createLogError } from '../common.js';
|
||||||
getErrorMessage as getErrorMessageShared,
|
|
||||||
createLogError,
|
|
||||||
} from "../common.js";
|
|
||||||
|
|
||||||
const logger = createLogger("Sessions");
|
const logger = createLogger('Sessions');
|
||||||
|
|
||||||
// Re-export shared utilities
|
// Re-export shared utilities
|
||||||
export { getErrorMessageShared as getErrorMessage };
|
export { getErrorMessageShared as getErrorMessage };
|
||||||
|
|||||||
@@ -2,24 +2,24 @@
|
|||||||
* Sessions routes - HTTP API for session management
|
* Sessions routes - HTTP API for session management
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router } from "express";
|
import { Router } from 'express';
|
||||||
import { AgentService } from "../../services/agent-service.js";
|
import { AgentService } from '../../services/agent-service.js';
|
||||||
import { createIndexHandler } from "./routes/index.js";
|
import { createIndexHandler } from './routes/index.js';
|
||||||
import { createCreateHandler } from "./routes/create.js";
|
import { createCreateHandler } from './routes/create.js';
|
||||||
import { createUpdateHandler } from "./routes/update.js";
|
import { createUpdateHandler } from './routes/update.js';
|
||||||
import { createArchiveHandler } from "./routes/archive.js";
|
import { createArchiveHandler } from './routes/archive.js';
|
||||||
import { createUnarchiveHandler } from "./routes/unarchive.js";
|
import { createUnarchiveHandler } from './routes/unarchive.js';
|
||||||
import { createDeleteHandler } from "./routes/delete.js";
|
import { createDeleteHandler } from './routes/delete.js';
|
||||||
|
|
||||||
export function createSessionsRoutes(agentService: AgentService): Router {
|
export function createSessionsRoutes(agentService: AgentService): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", createIndexHandler(agentService));
|
router.get('/', createIndexHandler(agentService));
|
||||||
router.post("/", createCreateHandler(agentService));
|
router.post('/', createCreateHandler(agentService));
|
||||||
router.put("/:sessionId", createUpdateHandler(agentService));
|
router.put('/:sessionId', createUpdateHandler(agentService));
|
||||||
router.post("/:sessionId/archive", createArchiveHandler(agentService));
|
router.post('/:sessionId/archive', createArchiveHandler(agentService));
|
||||||
router.post("/:sessionId/unarchive", createUnarchiveHandler(agentService));
|
router.post('/:sessionId/unarchive', createUnarchiveHandler(agentService));
|
||||||
router.delete("/:sessionId", createDeleteHandler(agentService));
|
router.delete('/:sessionId', createDeleteHandler(agentService));
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* POST /:sessionId/archive endpoint - Archive a session
|
* POST /:sessionId/archive endpoint - Archive a session
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { AgentService } from "../../../services/agent-service.js";
|
import { AgentService } from '../../../services/agent-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createArchiveHandler(agentService: AgentService) {
|
export function createArchiveHandler(agentService: AgentService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -13,13 +13,13 @@ export function createArchiveHandler(agentService: AgentService) {
|
|||||||
const success = await agentService.archiveSession(sessionId);
|
const success = await agentService.archiveSession(sessionId);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
res.status(404).json({ success: false, error: "Session not found" });
|
res.status(404).json({ success: false, error: 'Session not found' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Archive session failed");
|
logError(error, 'Archive session failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* POST / endpoint - Create a new session
|
* POST / endpoint - Create a new session
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { AgentService } from "../../../services/agent-service.js";
|
import { AgentService } from '../../../services/agent-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createCreateHandler(agentService: AgentService) {
|
export function createCreateHandler(agentService: AgentService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -17,19 +17,14 @@ export function createCreateHandler(agentService: AgentService) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
res.status(400).json({ success: false, error: "name is required" });
|
res.status(400).json({ success: false, error: 'name is required' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = await agentService.createSession(
|
const session = await agentService.createSession(name, projectPath, workingDirectory, model);
|
||||||
name,
|
|
||||||
projectPath,
|
|
||||||
workingDirectory,
|
|
||||||
model
|
|
||||||
);
|
|
||||||
res.json({ success: true, session });
|
res.json({ success: true, session });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Create session failed");
|
logError(error, 'Create session failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* DELETE /:sessionId endpoint - Delete a session
|
* DELETE /:sessionId endpoint - Delete a session
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { AgentService } from "../../../services/agent-service.js";
|
import { AgentService } from '../../../services/agent-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createDeleteHandler(agentService: AgentService) {
|
export function createDeleteHandler(agentService: AgentService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -13,13 +13,13 @@ export function createDeleteHandler(agentService: AgentService) {
|
|||||||
const success = await agentService.deleteSession(sessionId);
|
const success = await agentService.deleteSession(sessionId);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
res.status(404).json({ success: false, error: "Session not found" });
|
res.status(404).json({ success: false, error: 'Session not found' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Delete session failed");
|
logError(error, 'Delete session failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
* GET / endpoint - List all sessions
|
* GET / endpoint - List all sessions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { AgentService } from "../../../services/agent-service.js";
|
import { AgentService } from '../../../services/agent-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createIndexHandler(agentService: AgentService) {
|
export function createIndexHandler(agentService: AgentService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const includeArchived = req.query.includeArchived === "true";
|
const includeArchived = req.query.includeArchived === 'true';
|
||||||
const sessionsRaw = await agentService.listSessions(includeArchived);
|
const sessionsRaw = await agentService.listSessions(includeArchived);
|
||||||
|
|
||||||
// Transform to match frontend SessionListItem interface
|
// Transform to match frontend SessionListItem interface
|
||||||
@@ -17,7 +17,7 @@ export function createIndexHandler(agentService: AgentService) {
|
|||||||
sessionsRaw.map(async (s) => {
|
sessionsRaw.map(async (s) => {
|
||||||
const messages = await agentService.loadSession(s.id);
|
const messages = await agentService.loadSession(s.id);
|
||||||
const lastMessage = messages[messages.length - 1];
|
const lastMessage = messages[messages.length - 1];
|
||||||
const preview = lastMessage?.content?.slice(0, 100) || "";
|
const preview = lastMessage?.content?.slice(0, 100) || '';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: s.id,
|
id: s.id,
|
||||||
@@ -36,7 +36,7 @@ export function createIndexHandler(agentService: AgentService) {
|
|||||||
|
|
||||||
res.json({ success: true, sessions });
|
res.json({ success: true, sessions });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "List sessions failed");
|
logError(error, 'List sessions failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* POST /:sessionId/unarchive endpoint - Unarchive a session
|
* POST /:sessionId/unarchive endpoint - Unarchive a session
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { AgentService } from "../../../services/agent-service.js";
|
import { AgentService } from '../../../services/agent-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createUnarchiveHandler(agentService: AgentService) {
|
export function createUnarchiveHandler(agentService: AgentService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -13,13 +13,13 @@ export function createUnarchiveHandler(agentService: AgentService) {
|
|||||||
const success = await agentService.unarchiveSession(sessionId);
|
const success = await agentService.unarchiveSession(sessionId);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
res.status(404).json({ success: false, error: "Session not found" });
|
res.status(404).json({ success: false, error: 'Session not found' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Unarchive session failed");
|
logError(error, 'Unarchive session failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* PUT /:sessionId endpoint - Update a session
|
* PUT /:sessionId endpoint - Update a session
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { AgentService } from "../../../services/agent-service.js";
|
import { AgentService } from '../../../services/agent-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createUpdateHandler(agentService: AgentService) {
|
export function createUpdateHandler(agentService: AgentService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
@@ -22,13 +22,13 @@ export function createUpdateHandler(agentService: AgentService) {
|
|||||||
model,
|
model,
|
||||||
});
|
});
|
||||||
if (!session) {
|
if (!session) {
|
||||||
res.status(404).json({ success: false, error: "Session not found" });
|
res.status(404).json({ success: false, error: 'Session not found' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({ success: true, session });
|
res.json({ success: true, session });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Update session failed");
|
logError(error, 'Update session failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,14 +5,11 @@
|
|||||||
* Re-exports error handling helpers from the parent routes module.
|
* Re-exports error handling helpers from the parent routes module.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import {
|
import { getErrorMessage as getErrorMessageShared, createLogError } from '../common.js';
|
||||||
getErrorMessage as getErrorMessageShared,
|
|
||||||
createLogError,
|
|
||||||
} from "../common.js";
|
|
||||||
|
|
||||||
/** Logger instance for settings-related operations */
|
/** Logger instance for settings-related operations */
|
||||||
export const logger = createLogger("Settings");
|
export const logger = createLogger('Settings');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract user-friendly error message from error objects
|
* Extract user-friendly error message from error objects
|
||||||
|
|||||||
@@ -12,17 +12,17 @@
|
|||||||
* Mounted at /api/settings in the main server.
|
* Mounted at /api/settings in the main server.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router } from "express";
|
import { Router } from 'express';
|
||||||
import type { SettingsService } from "../../services/settings-service.js";
|
import type { SettingsService } from '../../services/settings-service.js';
|
||||||
import { validatePathParams } from "../../middleware/validate-paths.js";
|
import { validatePathParams } from '../../middleware/validate-paths.js';
|
||||||
import { createGetGlobalHandler } from "./routes/get-global.js";
|
import { createGetGlobalHandler } from './routes/get-global.js';
|
||||||
import { createUpdateGlobalHandler } from "./routes/update-global.js";
|
import { createUpdateGlobalHandler } from './routes/update-global.js';
|
||||||
import { createGetCredentialsHandler } from "./routes/get-credentials.js";
|
import { createGetCredentialsHandler } from './routes/get-credentials.js';
|
||||||
import { createUpdateCredentialsHandler } from "./routes/update-credentials.js";
|
import { createUpdateCredentialsHandler } from './routes/update-credentials.js';
|
||||||
import { createGetProjectHandler } from "./routes/get-project.js";
|
import { createGetProjectHandler } from './routes/get-project.js';
|
||||||
import { createUpdateProjectHandler } from "./routes/update-project.js";
|
import { createUpdateProjectHandler } from './routes/update-project.js';
|
||||||
import { createMigrateHandler } from "./routes/migrate.js";
|
import { createMigrateHandler } from './routes/migrate.js';
|
||||||
import { createStatusHandler } from "./routes/status.js";
|
import { createStatusHandler } from './routes/status.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create settings router with all endpoints
|
* Create settings router with all endpoints
|
||||||
@@ -47,22 +47,30 @@ export function createSettingsRoutes(settingsService: SettingsService): Router {
|
|||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
// Status endpoint (check if migration needed)
|
// Status endpoint (check if migration needed)
|
||||||
router.get("/status", createStatusHandler(settingsService));
|
router.get('/status', createStatusHandler(settingsService));
|
||||||
|
|
||||||
// Global settings
|
// Global settings
|
||||||
router.get("/global", createGetGlobalHandler(settingsService));
|
router.get('/global', createGetGlobalHandler(settingsService));
|
||||||
router.put("/global", createUpdateGlobalHandler(settingsService));
|
router.put('/global', createUpdateGlobalHandler(settingsService));
|
||||||
|
|
||||||
// Credentials (separate for security)
|
// Credentials (separate for security)
|
||||||
router.get("/credentials", createGetCredentialsHandler(settingsService));
|
router.get('/credentials', createGetCredentialsHandler(settingsService));
|
||||||
router.put("/credentials", createUpdateCredentialsHandler(settingsService));
|
router.put('/credentials', createUpdateCredentialsHandler(settingsService));
|
||||||
|
|
||||||
// Project settings
|
// Project settings
|
||||||
router.post("/project", validatePathParams("projectPath"), createGetProjectHandler(settingsService));
|
router.post(
|
||||||
router.put("/project", validatePathParams("projectPath"), createUpdateProjectHandler(settingsService));
|
'/project',
|
||||||
|
validatePathParams('projectPath'),
|
||||||
|
createGetProjectHandler(settingsService)
|
||||||
|
);
|
||||||
|
router.put(
|
||||||
|
'/project',
|
||||||
|
validatePathParams('projectPath'),
|
||||||
|
createUpdateProjectHandler(settingsService)
|
||||||
|
);
|
||||||
|
|
||||||
// Migration from localStorage
|
// Migration from localStorage
|
||||||
router.post("/migrate", createMigrateHandler(settingsService));
|
router.post('/migrate', createMigrateHandler(settingsService));
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
* Response: `{ "success": true, "credentials": { anthropic } }`
|
* Response: `{ "success": true, "credentials": { anthropic } }`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { SettingsService } from "../../../services/settings-service.js";
|
import type { SettingsService } from '../../../services/settings-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create handler factory for GET /api/settings/credentials
|
* Create handler factory for GET /api/settings/credentials
|
||||||
@@ -28,7 +28,7 @@ export function createGetCredentialsHandler(settingsService: SettingsService) {
|
|||||||
credentials,
|
credentials,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Get credentials failed");
|
logError(error, 'Get credentials failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
* Response: `{ "success": true, "settings": GlobalSettings }`
|
* Response: `{ "success": true, "settings": GlobalSettings }`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { SettingsService } from "../../../services/settings-service.js";
|
import type { SettingsService } from '../../../services/settings-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create handler factory for GET /api/settings/global
|
* Create handler factory for GET /api/settings/global
|
||||||
@@ -27,7 +27,7 @@ export function createGetGlobalHandler(settingsService: SettingsService) {
|
|||||||
settings,
|
settings,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Get global settings failed");
|
logError(error, 'Get global settings failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
* Response: `{ "success": true, "settings": ProjectSettings }`
|
* Response: `{ "success": true, "settings": ProjectSettings }`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { SettingsService } from "../../../services/settings-service.js";
|
import type { SettingsService } from '../../../services/settings-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create handler factory for POST /api/settings/project
|
* Create handler factory for POST /api/settings/project
|
||||||
@@ -23,10 +23,10 @@ export function createGetProjectHandler(settingsService: SettingsService) {
|
|||||||
try {
|
try {
|
||||||
const { projectPath } = req.body as { projectPath?: string };
|
const { projectPath } = req.body as { projectPath?: string };
|
||||||
|
|
||||||
if (!projectPath || typeof projectPath !== "string") {
|
if (!projectPath || typeof projectPath !== 'string') {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "projectPath is required",
|
error: 'projectPath is required',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ export function createGetProjectHandler(settingsService: SettingsService) {
|
|||||||
settings,
|
settings,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Get project settings failed");
|
logError(error, 'Get project settings failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,9 +30,9 @@
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { SettingsService } from "../../../services/settings-service.js";
|
import type { SettingsService } from '../../../services/settings-service.js';
|
||||||
import { getErrorMessage, logError, logger } from "../common.js";
|
import { getErrorMessage, logError, logger } from '../common.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create handler factory for POST /api/settings/migrate
|
* Create handler factory for POST /api/settings/migrate
|
||||||
@@ -45,32 +45,30 @@ export function createMigrateHandler(settingsService: SettingsService) {
|
|||||||
try {
|
try {
|
||||||
const { data } = req.body as {
|
const { data } = req.body as {
|
||||||
data?: {
|
data?: {
|
||||||
"automaker-storage"?: string;
|
'automaker-storage'?: string;
|
||||||
"automaker-setup"?: string;
|
'automaker-setup'?: string;
|
||||||
"worktree-panel-collapsed"?: string;
|
'worktree-panel-collapsed'?: string;
|
||||||
"file-browser-recent-folders"?: string;
|
'file-browser-recent-folders'?: string;
|
||||||
"automaker:lastProjectDir"?: string;
|
'automaker:lastProjectDir'?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!data || typeof data !== "object") {
|
if (!data || typeof data !== 'object') {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "data object is required containing localStorage data",
|
error: 'data object is required containing localStorage data',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Starting settings migration from localStorage");
|
logger.info('Starting settings migration from localStorage');
|
||||||
|
|
||||||
const result = await settingsService.migrateFromLocalStorage(data);
|
const result = await settingsService.migrateFromLocalStorage(data);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
logger.info(
|
logger.info(`Migration successful: ${result.migratedProjectCount} projects migrated`);
|
||||||
`Migration successful: ${result.migratedProjectCount} projects migrated`
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
logger.warn(`Migration completed with errors: ${result.errors.join(", ")}`);
|
logger.warn(`Migration completed with errors: ${result.errors.join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
@@ -81,7 +79,7 @@ export function createMigrateHandler(settingsService: SettingsService) {
|
|||||||
errors: result.errors,
|
errors: result.errors,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Migration failed");
|
logError(error, 'Migration failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,9 +16,9 @@
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { SettingsService } from "../../../services/settings-service.js";
|
import type { SettingsService } from '../../../services/settings-service.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create handler factory for GET /api/settings/status
|
* Create handler factory for GET /api/settings/status
|
||||||
@@ -40,7 +40,7 @@ export function createStatusHandler(settingsService: SettingsService) {
|
|||||||
needsMigration: !hasGlobalSettings,
|
needsMigration: !hasGlobalSettings,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Get settings status failed");
|
logError(error, 'Get settings status failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
* Response: `{ "success": true, "credentials": { anthropic } }`
|
* Response: `{ "success": true, "credentials": { anthropic } }`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { SettingsService } from "../../../services/settings-service.js";
|
import type { SettingsService } from '../../../services/settings-service.js';
|
||||||
import type { Credentials } from "../../../types/settings.js";
|
import type { Credentials } from '../../../types/settings.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create handler factory for PUT /api/settings/credentials
|
* Create handler factory for PUT /api/settings/credentials
|
||||||
@@ -19,17 +19,15 @@ import { getErrorMessage, logError } from "../common.js";
|
|||||||
* @param settingsService - Instance of SettingsService for file I/O
|
* @param settingsService - Instance of SettingsService for file I/O
|
||||||
* @returns Express request handler
|
* @returns Express request handler
|
||||||
*/
|
*/
|
||||||
export function createUpdateCredentialsHandler(
|
export function createUpdateCredentialsHandler(settingsService: SettingsService) {
|
||||||
settingsService: SettingsService
|
|
||||||
) {
|
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const updates = req.body as Partial<Credentials>;
|
const updates = req.body as Partial<Credentials>;
|
||||||
|
|
||||||
if (!updates || typeof updates !== "object") {
|
if (!updates || typeof updates !== 'object') {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "Invalid request body - expected credentials object",
|
error: 'Invalid request body - expected credentials object',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -44,7 +42,7 @@ export function createUpdateCredentialsHandler(
|
|||||||
credentials: masked,
|
credentials: masked,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Update credentials failed");
|
logError(error, 'Update credentials failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
* Response: `{ "success": true, "settings": GlobalSettings }`
|
* Response: `{ "success": true, "settings": GlobalSettings }`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { SettingsService } from "../../../services/settings-service.js";
|
import type { SettingsService } from '../../../services/settings-service.js';
|
||||||
import type { GlobalSettings } from "../../../types/settings.js";
|
import type { GlobalSettings } from '../../../types/settings.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create handler factory for PUT /api/settings/global
|
* Create handler factory for PUT /api/settings/global
|
||||||
@@ -24,10 +24,10 @@ export function createUpdateGlobalHandler(settingsService: SettingsService) {
|
|||||||
try {
|
try {
|
||||||
const updates = req.body as Partial<GlobalSettings>;
|
const updates = req.body as Partial<GlobalSettings>;
|
||||||
|
|
||||||
if (!updates || typeof updates !== "object") {
|
if (!updates || typeof updates !== 'object') {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "Invalid request body - expected settings object",
|
error: 'Invalid request body - expected settings object',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ export function createUpdateGlobalHandler(settingsService: SettingsService) {
|
|||||||
settings,
|
settings,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Update global settings failed");
|
logError(error, 'Update global settings failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
* Response: `{ "success": true, "settings": ProjectSettings }`
|
* Response: `{ "success": true, "settings": ProjectSettings }`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import type { SettingsService } from "../../../services/settings-service.js";
|
import type { SettingsService } from '../../../services/settings-service.js';
|
||||||
import type { ProjectSettings } from "../../../types/settings.js";
|
import type { ProjectSettings } from '../../../types/settings.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create handler factory for PUT /api/settings/project
|
* Create handler factory for PUT /api/settings/project
|
||||||
@@ -27,33 +27,30 @@ export function createUpdateProjectHandler(settingsService: SettingsService) {
|
|||||||
updates?: Partial<ProjectSettings>;
|
updates?: Partial<ProjectSettings>;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!projectPath || typeof projectPath !== "string") {
|
if (!projectPath || typeof projectPath !== 'string') {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "projectPath is required",
|
error: 'projectPath is required',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!updates || typeof updates !== "object") {
|
if (!updates || typeof updates !== 'object') {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "updates object is required",
|
error: 'updates object is required',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = await settingsService.updateProjectSettings(
|
const settings = await settingsService.updateProjectSettings(projectPath, updates);
|
||||||
projectPath,
|
|
||||||
updates
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
settings,
|
settings,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Update project settings failed");
|
logError(error, 'Update project settings failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,15 +2,12 @@
|
|||||||
* Common utilities and state for setup routes
|
* Common utilities and state for setup routes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import path from "path";
|
import path from 'path';
|
||||||
import fs from "fs/promises";
|
import fs from 'fs/promises';
|
||||||
import {
|
import { getErrorMessage as getErrorMessageShared, createLogError } from '../common.js';
|
||||||
getErrorMessage as getErrorMessageShared,
|
|
||||||
createLogError,
|
|
||||||
} from "../common.js";
|
|
||||||
|
|
||||||
const logger = createLogger("Setup");
|
const logger = createLogger('Setup');
|
||||||
|
|
||||||
// Storage for API keys (in-memory cache) - private
|
// Storage for API keys (in-memory cache) - private
|
||||||
const apiKeys: Record<string, string> = {};
|
const apiKeys: Record<string, string> = {};
|
||||||
@@ -39,22 +36,19 @@ export function getAllApiKeys(): Record<string, string> {
|
|||||||
/**
|
/**
|
||||||
* Helper to persist API keys to .env file
|
* Helper to persist API keys to .env file
|
||||||
*/
|
*/
|
||||||
export async function persistApiKeyToEnv(
|
export async function persistApiKeyToEnv(key: string, value: string): Promise<void> {
|
||||||
key: string,
|
const envPath = path.join(process.cwd(), '.env');
|
||||||
value: string
|
|
||||||
): Promise<void> {
|
|
||||||
const envPath = path.join(process.cwd(), ".env");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let envContent = "";
|
let envContent = '';
|
||||||
try {
|
try {
|
||||||
envContent = await fs.readFile(envPath, "utf-8");
|
envContent = await fs.readFile(envPath, 'utf-8');
|
||||||
} catch {
|
} catch {
|
||||||
// .env file doesn't exist, we'll create it
|
// .env file doesn't exist, we'll create it
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse existing env content
|
// Parse existing env content
|
||||||
const lines = envContent.split("\n");
|
const lines = envContent.split('\n');
|
||||||
const keyRegex = new RegExp(`^${key}=`);
|
const keyRegex = new RegExp(`^${key}=`);
|
||||||
let found = false;
|
let found = false;
|
||||||
const newLines = lines.map((line) => {
|
const newLines = lines.map((line) => {
|
||||||
@@ -70,7 +64,7 @@ export async function persistApiKeyToEnv(
|
|||||||
newLines.push(`${key}=${value}`);
|
newLines.push(`${key}=${value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.writeFile(envPath, newLines.join("\n"));
|
await fs.writeFile(envPath, newLines.join('\n'));
|
||||||
logger.info(`[Setup] Persisted ${key} to .env file`);
|
logger.info(`[Setup] Persisted ${key} to .env file`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[Setup] Failed to persist ${key} to .env:`, error);
|
logger.error(`[Setup] Failed to persist ${key} to .env:`, error);
|
||||||
|
|||||||
@@ -2,36 +2,36 @@
|
|||||||
* Business logic for getting Claude CLI status
|
* Business logic for getting Claude CLI status
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { exec } from "child_process";
|
import { exec } from 'child_process';
|
||||||
import { promisify } from "util";
|
import { promisify } from 'util';
|
||||||
import os from "os";
|
import os from 'os';
|
||||||
import path from "path";
|
import path from 'path';
|
||||||
import fs from "fs/promises";
|
import fs from 'fs/promises';
|
||||||
import { getApiKey } from "./common.js";
|
import { getApiKey } from './common.js';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
export async function getClaudeStatus() {
|
export async function getClaudeStatus() {
|
||||||
let installed = false;
|
let installed = false;
|
||||||
let version = "";
|
let version = '';
|
||||||
let cliPath = "";
|
let cliPath = '';
|
||||||
let method = "none";
|
let method = 'none';
|
||||||
|
|
||||||
const isWindows = process.platform === "win32";
|
const isWindows = process.platform === 'win32';
|
||||||
|
|
||||||
// Try to find Claude CLI using platform-specific command
|
// Try to find Claude CLI using platform-specific command
|
||||||
try {
|
try {
|
||||||
// Use 'where' on Windows, 'which' on Unix-like systems
|
// Use 'where' on Windows, 'which' on Unix-like systems
|
||||||
const findCommand = isWindows ? "where claude" : "which claude";
|
const findCommand = isWindows ? 'where claude' : 'which claude';
|
||||||
const { stdout } = await execAsync(findCommand);
|
const { stdout } = await execAsync(findCommand);
|
||||||
// 'where' on Windows can return multiple paths - take the first one
|
// 'where' on Windows can return multiple paths - take the first one
|
||||||
cliPath = stdout.trim().split(/\r?\n/)[0];
|
cliPath = stdout.trim().split(/\r?\n/)[0];
|
||||||
installed = true;
|
installed = true;
|
||||||
method = "path";
|
method = 'path';
|
||||||
|
|
||||||
// Get version
|
// Get version
|
||||||
try {
|
try {
|
||||||
const { stdout: versionOut } = await execAsync("claude --version");
|
const { stdout: versionOut } = await execAsync('claude --version');
|
||||||
version = versionOut.trim();
|
version = versionOut.trim();
|
||||||
} catch {
|
} catch {
|
||||||
// Version command might not be available
|
// Version command might not be available
|
||||||
@@ -40,22 +40,22 @@ export async function getClaudeStatus() {
|
|||||||
// Not in PATH, try common locations based on platform
|
// Not in PATH, try common locations based on platform
|
||||||
const commonPaths = isWindows
|
const commonPaths = isWindows
|
||||||
? (() => {
|
? (() => {
|
||||||
const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
|
const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
|
||||||
return [
|
return [
|
||||||
// Windows-specific paths
|
// Windows-specific paths
|
||||||
path.join(os.homedir(), ".local", "bin", "claude.exe"),
|
path.join(os.homedir(), '.local', 'bin', 'claude.exe'),
|
||||||
path.join(appData, "npm", "claude.cmd"),
|
path.join(appData, 'npm', 'claude.cmd'),
|
||||||
path.join(appData, "npm", "claude"),
|
path.join(appData, 'npm', 'claude'),
|
||||||
path.join(appData, ".npm-global", "bin", "claude.cmd"),
|
path.join(appData, '.npm-global', 'bin', 'claude.cmd'),
|
||||||
path.join(appData, ".npm-global", "bin", "claude"),
|
path.join(appData, '.npm-global', 'bin', 'claude'),
|
||||||
];
|
];
|
||||||
})()
|
})()
|
||||||
: [
|
: [
|
||||||
// Unix (Linux/macOS) paths
|
// Unix (Linux/macOS) paths
|
||||||
path.join(os.homedir(), ".local", "bin", "claude"),
|
path.join(os.homedir(), '.local', 'bin', 'claude'),
|
||||||
path.join(os.homedir(), ".claude", "local", "claude"),
|
path.join(os.homedir(), '.claude', 'local', 'claude'),
|
||||||
"/usr/local/bin/claude",
|
'/usr/local/bin/claude',
|
||||||
path.join(os.homedir(), ".npm-global", "bin", "claude"),
|
path.join(os.homedir(), '.npm-global', 'bin', 'claude'),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const p of commonPaths) {
|
for (const p of commonPaths) {
|
||||||
@@ -63,7 +63,7 @@ export async function getClaudeStatus() {
|
|||||||
await fs.access(p);
|
await fs.access(p);
|
||||||
cliPath = p;
|
cliPath = p;
|
||||||
installed = true;
|
installed = true;
|
||||||
method = "local";
|
method = 'local';
|
||||||
|
|
||||||
// Get version from this path
|
// Get version from this path
|
||||||
try {
|
try {
|
||||||
@@ -84,11 +84,11 @@ export async function getClaudeStatus() {
|
|||||||
// apiKeys.anthropic stores direct API keys for pay-per-use
|
// apiKeys.anthropic stores direct API keys for pay-per-use
|
||||||
let auth = {
|
let auth = {
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
method: "none" as string,
|
method: 'none' as string,
|
||||||
hasCredentialsFile: false,
|
hasCredentialsFile: false,
|
||||||
hasToken: false,
|
hasToken: false,
|
||||||
hasStoredOAuthToken: !!getApiKey("anthropic_oauth_token"),
|
hasStoredOAuthToken: !!getApiKey('anthropic_oauth_token'),
|
||||||
hasStoredApiKey: !!getApiKey("anthropic"),
|
hasStoredApiKey: !!getApiKey('anthropic'),
|
||||||
hasEnvApiKey: !!process.env.ANTHROPIC_API_KEY,
|
hasEnvApiKey: !!process.env.ANTHROPIC_API_KEY,
|
||||||
// Additional fields for detailed status
|
// Additional fields for detailed status
|
||||||
oauthTokenValid: false,
|
oauthTokenValid: false,
|
||||||
@@ -97,13 +97,13 @@ export async function getClaudeStatus() {
|
|||||||
hasRecentActivity: false,
|
hasRecentActivity: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const claudeDir = path.join(os.homedir(), ".claude");
|
const claudeDir = path.join(os.homedir(), '.claude');
|
||||||
|
|
||||||
// Check for recent Claude CLI activity - indicates working authentication
|
// Check for recent Claude CLI activity - indicates working authentication
|
||||||
// The stats-cache.json file is only populated when the CLI is working properly
|
// The stats-cache.json file is only populated when the CLI is working properly
|
||||||
const statsCachePath = path.join(claudeDir, "stats-cache.json");
|
const statsCachePath = path.join(claudeDir, 'stats-cache.json');
|
||||||
try {
|
try {
|
||||||
const statsContent = await fs.readFile(statsCachePath, "utf-8");
|
const statsContent = await fs.readFile(statsCachePath, 'utf-8');
|
||||||
const stats = JSON.parse(statsContent);
|
const stats = JSON.parse(statsContent);
|
||||||
|
|
||||||
// Check if there's any activity (which means the CLI is authenticated and working)
|
// Check if there's any activity (which means the CLI is authenticated and working)
|
||||||
@@ -111,26 +111,26 @@ export async function getClaudeStatus() {
|
|||||||
auth.hasRecentActivity = true;
|
auth.hasRecentActivity = true;
|
||||||
auth.hasCliAuth = true;
|
auth.hasCliAuth = true;
|
||||||
auth.authenticated = true;
|
auth.authenticated = true;
|
||||||
auth.method = "cli_authenticated";
|
auth.method = 'cli_authenticated';
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Stats file doesn't exist or is invalid
|
// Stats file doesn't exist or is invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for settings.json - indicates CLI has been set up
|
// Check for settings.json - indicates CLI has been set up
|
||||||
const settingsPath = path.join(claudeDir, "settings.json");
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
||||||
try {
|
try {
|
||||||
await fs.access(settingsPath);
|
await fs.access(settingsPath);
|
||||||
// If settings exist but no activity, CLI might be set up but not authenticated
|
// If settings exist but no activity, CLI might be set up but not authenticated
|
||||||
if (!auth.hasCliAuth) {
|
if (!auth.hasCliAuth) {
|
||||||
// Try to check for other indicators of auth
|
// Try to check for other indicators of auth
|
||||||
const sessionsDir = path.join(claudeDir, "projects");
|
const sessionsDir = path.join(claudeDir, 'projects');
|
||||||
try {
|
try {
|
||||||
const sessions = await fs.readdir(sessionsDir);
|
const sessions = await fs.readdir(sessionsDir);
|
||||||
if (sessions.length > 0) {
|
if (sessions.length > 0) {
|
||||||
auth.hasCliAuth = true;
|
auth.hasCliAuth = true;
|
||||||
auth.authenticated = true;
|
auth.authenticated = true;
|
||||||
auth.method = "cli_authenticated";
|
auth.method = 'cli_authenticated';
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Sessions directory doesn't exist
|
// Sessions directory doesn't exist
|
||||||
@@ -143,13 +143,13 @@ export async function getClaudeStatus() {
|
|||||||
// Check for credentials file (OAuth tokens from claude login)
|
// Check for credentials file (OAuth tokens from claude login)
|
||||||
// Note: Claude CLI may use ".credentials.json" (hidden) or "credentials.json" depending on version/platform
|
// Note: Claude CLI may use ".credentials.json" (hidden) or "credentials.json" depending on version/platform
|
||||||
const credentialsPaths = [
|
const credentialsPaths = [
|
||||||
path.join(claudeDir, ".credentials.json"),
|
path.join(claudeDir, '.credentials.json'),
|
||||||
path.join(claudeDir, "credentials.json"),
|
path.join(claudeDir, 'credentials.json'),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const credentialsPath of credentialsPaths) {
|
for (const credentialsPath of credentialsPaths) {
|
||||||
try {
|
try {
|
||||||
const credentialsContent = await fs.readFile(credentialsPath, "utf-8");
|
const credentialsContent = await fs.readFile(credentialsPath, 'utf-8');
|
||||||
const credentials = JSON.parse(credentialsContent);
|
const credentials = JSON.parse(credentialsContent);
|
||||||
auth.hasCredentialsFile = true;
|
auth.hasCredentialsFile = true;
|
||||||
|
|
||||||
@@ -158,11 +158,11 @@ export async function getClaudeStatus() {
|
|||||||
auth.hasStoredOAuthToken = true;
|
auth.hasStoredOAuthToken = true;
|
||||||
auth.oauthTokenValid = true;
|
auth.oauthTokenValid = true;
|
||||||
auth.authenticated = true;
|
auth.authenticated = true;
|
||||||
auth.method = "oauth_token"; // Stored OAuth token from credentials file
|
auth.method = 'oauth_token'; // Stored OAuth token from credentials file
|
||||||
} else if (credentials.api_key) {
|
} else if (credentials.api_key) {
|
||||||
auth.apiKeyValid = true;
|
auth.apiKeyValid = true;
|
||||||
auth.authenticated = true;
|
auth.authenticated = true;
|
||||||
auth.method = "api_key"; // Stored API key in credentials file
|
auth.method = 'api_key'; // Stored API key in credentials file
|
||||||
}
|
}
|
||||||
break; // Found and processed credentials file
|
break; // Found and processed credentials file
|
||||||
} catch {
|
} catch {
|
||||||
@@ -174,25 +174,25 @@ export async function getClaudeStatus() {
|
|||||||
if (auth.hasEnvApiKey) {
|
if (auth.hasEnvApiKey) {
|
||||||
auth.authenticated = true;
|
auth.authenticated = true;
|
||||||
auth.apiKeyValid = true;
|
auth.apiKeyValid = true;
|
||||||
auth.method = "api_key_env"; // API key from ANTHROPIC_API_KEY env var
|
auth.method = 'api_key_env'; // API key from ANTHROPIC_API_KEY env var
|
||||||
}
|
}
|
||||||
|
|
||||||
// In-memory stored OAuth token (from setup wizard - subscription auth)
|
// In-memory stored OAuth token (from setup wizard - subscription auth)
|
||||||
if (!auth.authenticated && getApiKey("anthropic_oauth_token")) {
|
if (!auth.authenticated && getApiKey('anthropic_oauth_token')) {
|
||||||
auth.authenticated = true;
|
auth.authenticated = true;
|
||||||
auth.oauthTokenValid = true;
|
auth.oauthTokenValid = true;
|
||||||
auth.method = "oauth_token"; // Stored OAuth token from setup wizard
|
auth.method = 'oauth_token'; // Stored OAuth token from setup wizard
|
||||||
}
|
}
|
||||||
|
|
||||||
// In-memory stored API key (from settings UI - pay-per-use)
|
// In-memory stored API key (from settings UI - pay-per-use)
|
||||||
if (!auth.authenticated && getApiKey("anthropic")) {
|
if (!auth.authenticated && getApiKey('anthropic')) {
|
||||||
auth.authenticated = true;
|
auth.authenticated = true;
|
||||||
auth.apiKeyValid = true;
|
auth.apiKeyValid = true;
|
||||||
auth.method = "api_key"; // Manually stored API key
|
auth.method = 'api_key'; // Manually stored API key
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: installed ? "installed" : "not_installed",
|
status: installed ? 'installed' : 'not_installed',
|
||||||
installed,
|
installed,
|
||||||
method,
|
method,
|
||||||
version,
|
version,
|
||||||
|
|||||||
@@ -2,29 +2,29 @@
|
|||||||
* Setup routes - HTTP API for CLI detection, API keys, and platform info
|
* Setup routes - HTTP API for CLI detection, API keys, and platform info
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router } from "express";
|
import { Router } from 'express';
|
||||||
import { createClaudeStatusHandler } from "./routes/claude-status.js";
|
import { createClaudeStatusHandler } from './routes/claude-status.js';
|
||||||
import { createInstallClaudeHandler } from "./routes/install-claude.js";
|
import { createInstallClaudeHandler } from './routes/install-claude.js';
|
||||||
import { createAuthClaudeHandler } from "./routes/auth-claude.js";
|
import { createAuthClaudeHandler } from './routes/auth-claude.js';
|
||||||
import { createStoreApiKeyHandler } from "./routes/store-api-key.js";
|
import { createStoreApiKeyHandler } from './routes/store-api-key.js';
|
||||||
import { createDeleteApiKeyHandler } from "./routes/delete-api-key.js";
|
import { createDeleteApiKeyHandler } from './routes/delete-api-key.js';
|
||||||
import { createApiKeysHandler } from "./routes/api-keys.js";
|
import { createApiKeysHandler } from './routes/api-keys.js';
|
||||||
import { createPlatformHandler } from "./routes/platform.js";
|
import { createPlatformHandler } from './routes/platform.js';
|
||||||
import { createVerifyClaudeAuthHandler } from "./routes/verify-claude-auth.js";
|
import { createVerifyClaudeAuthHandler } from './routes/verify-claude-auth.js';
|
||||||
import { createGhStatusHandler } from "./routes/gh-status.js";
|
import { createGhStatusHandler } from './routes/gh-status.js';
|
||||||
|
|
||||||
export function createSetupRoutes(): Router {
|
export function createSetupRoutes(): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/claude-status", createClaudeStatusHandler());
|
router.get('/claude-status', createClaudeStatusHandler());
|
||||||
router.post("/install-claude", createInstallClaudeHandler());
|
router.post('/install-claude', createInstallClaudeHandler());
|
||||||
router.post("/auth-claude", createAuthClaudeHandler());
|
router.post('/auth-claude', createAuthClaudeHandler());
|
||||||
router.post("/store-api-key", createStoreApiKeyHandler());
|
router.post('/store-api-key', createStoreApiKeyHandler());
|
||||||
router.post("/delete-api-key", createDeleteApiKeyHandler());
|
router.post('/delete-api-key', createDeleteApiKeyHandler());
|
||||||
router.get("/api-keys", createApiKeysHandler());
|
router.get('/api-keys', createApiKeysHandler());
|
||||||
router.get("/platform", createPlatformHandler());
|
router.get('/platform', createPlatformHandler());
|
||||||
router.post("/verify-claude-auth", createVerifyClaudeAuthHandler());
|
router.post('/verify-claude-auth', createVerifyClaudeAuthHandler());
|
||||||
router.get("/gh-status", createGhStatusHandler());
|
router.get('/gh-status', createGhStatusHandler());
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,18 @@
|
|||||||
* GET /api-keys endpoint - Get API keys status
|
* GET /api-keys endpoint - Get API keys status
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { getApiKey, getErrorMessage, logError } from "../common.js";
|
import { getApiKey, getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createApiKeysHandler() {
|
export function createApiKeysHandler() {
|
||||||
return async (_req: Request, res: Response): Promise<void> => {
|
return async (_req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
hasAnthropicKey:
|
hasAnthropicKey: !!getApiKey('anthropic') || !!process.env.ANTHROPIC_API_KEY,
|
||||||
!!getApiKey("anthropic") || !!process.env.ANTHROPIC_API_KEY,
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Get API keys failed");
|
logError(error, 'Get API keys failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
* POST /auth-claude endpoint - Auth Claude
|
* POST /auth-claude endpoint - Auth Claude
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createAuthClaudeHandler() {
|
export function createAuthClaudeHandler() {
|
||||||
return async (_req: Request, res: Response): Promise<void> => {
|
return async (_req: Request, res: Response): Promise<void> => {
|
||||||
@@ -11,11 +11,11 @@ export function createAuthClaudeHandler() {
|
|||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
requiresManualAuth: true,
|
requiresManualAuth: true,
|
||||||
command: "claude login",
|
command: 'claude login',
|
||||||
message: "Please run 'claude login' in your terminal to authenticate",
|
message: "Please run 'claude login' in your terminal to authenticate",
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Auth Claude failed");
|
logError(error, 'Auth Claude failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* GET /claude-status endpoint - Get Claude CLI status
|
* GET /claude-status endpoint - Get Claude CLI status
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { getClaudeStatus } from "../get-claude-status.js";
|
import { getClaudeStatus } from '../get-claude-status.js';
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
export function createClaudeStatusHandler() {
|
export function createClaudeStatusHandler() {
|
||||||
return async (_req: Request, res: Response): Promise<void> => {
|
return async (_req: Request, res: Response): Promise<void> => {
|
||||||
@@ -15,7 +15,7 @@ export function createClaudeStatusHandler() {
|
|||||||
...status,
|
...status,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "Get Claude status failed");
|
logError(error, 'Get Claude status failed');
|
||||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,43 +2,43 @@
|
|||||||
* POST /delete-api-key endpoint - Delete a stored API key
|
* POST /delete-api-key endpoint - Delete a stored API key
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from 'express';
|
||||||
import { createLogger } from "@automaker/utils";
|
import { createLogger } from '@automaker/utils';
|
||||||
import path from "path";
|
import path from 'path';
|
||||||
import fs from "fs/promises";
|
import fs from 'fs/promises';
|
||||||
|
|
||||||
const logger = createLogger("Setup");
|
const logger = createLogger('Setup');
|
||||||
|
|
||||||
// In-memory storage reference (imported from common.ts pattern)
|
// In-memory storage reference (imported from common.ts pattern)
|
||||||
// We need to modify common.ts to export a deleteApiKey function
|
// We need to modify common.ts to export a deleteApiKey function
|
||||||
import { setApiKey } from "../common.js";
|
import { setApiKey } from '../common.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an API key from the .env file
|
* Remove an API key from the .env file
|
||||||
*/
|
*/
|
||||||
async function removeApiKeyFromEnv(key: string): Promise<void> {
|
async function removeApiKeyFromEnv(key: string): Promise<void> {
|
||||||
const envPath = path.join(process.cwd(), ".env");
|
const envPath = path.join(process.cwd(), '.env');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let envContent = "";
|
let envContent = '';
|
||||||
try {
|
try {
|
||||||
envContent = await fs.readFile(envPath, "utf-8");
|
envContent = await fs.readFile(envPath, 'utf-8');
|
||||||
} catch {
|
} catch {
|
||||||
// .env file doesn't exist, nothing to delete
|
// .env file doesn't exist, nothing to delete
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse existing env content and remove the key
|
// Parse existing env content and remove the key
|
||||||
const lines = envContent.split("\n");
|
const lines = envContent.split('\n');
|
||||||
const keyRegex = new RegExp(`^${key}=`);
|
const keyRegex = new RegExp(`^${key}=`);
|
||||||
const newLines = lines.filter((line) => !keyRegex.test(line));
|
const newLines = lines.filter((line) => !keyRegex.test(line));
|
||||||
|
|
||||||
// Remove empty lines at the end
|
// Remove empty lines at the end
|
||||||
while (newLines.length > 0 && newLines[newLines.length - 1].trim() === "") {
|
while (newLines.length > 0 && newLines[newLines.length - 1].trim() === '') {
|
||||||
newLines.pop();
|
newLines.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.writeFile(envPath, newLines.join("\n") + (newLines.length > 0 ? "\n" : ""));
|
await fs.writeFile(envPath, newLines.join('\n') + (newLines.length > 0 ? '\n' : ''));
|
||||||
logger.info(`[Setup] Removed ${key} from .env file`);
|
logger.info(`[Setup] Removed ${key} from .env file`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[Setup] Failed to remove ${key} from .env:`, error);
|
logger.error(`[Setup] Failed to remove ${key} from .env:`, error);
|
||||||
@@ -54,7 +54,7 @@ export function createDeleteApiKeyHandler() {
|
|||||||
if (!provider) {
|
if (!provider) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "Provider is required",
|
error: 'Provider is required',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@ export function createDeleteApiKeyHandler() {
|
|||||||
|
|
||||||
// Map provider to env key name
|
// Map provider to env key name
|
||||||
const envKeyMap: Record<string, string> = {
|
const envKeyMap: Record<string, string> = {
|
||||||
anthropic: "ANTHROPIC_API_KEY",
|
anthropic: 'ANTHROPIC_API_KEY',
|
||||||
};
|
};
|
||||||
|
|
||||||
const envKey = envKeyMap[provider];
|
const envKey = envKeyMap[provider];
|
||||||
@@ -76,7 +76,7 @@ export function createDeleteApiKeyHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear from in-memory storage
|
// Clear from in-memory storage
|
||||||
setApiKey(provider, "");
|
setApiKey(provider, '');
|
||||||
|
|
||||||
// Remove from environment
|
// Remove from environment
|
||||||
delete process.env[envKey];
|
delete process.env[envKey];
|
||||||
@@ -91,14 +91,11 @@ export function createDeleteApiKeyHandler() {
|
|||||||
message: `API key for ${provider} has been deleted`,
|
message: `API key for ${provider} has been deleted`,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("[Setup] Delete API key error:", error);
|
logger.error('[Setup] Delete API key error:', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: error instanceof Error ? error.message : "Failed to delete API key",
|
error: error instanceof Error ? error.message : 'Failed to delete API key',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user