mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
Merge pull request #486 from AutoMaker-Org/fix/claude-usage-parsing
fix: Claude usage parsing for CLI v2.x and trust prompt handling
This commit is contained in:
@@ -34,6 +34,13 @@ export function createClaudeRoutes(service: ClaudeUsageService): Router {
|
||||
error: 'Authentication required',
|
||||
message: "Please run 'claude login' to authenticate",
|
||||
});
|
||||
} else if (message.includes('TRUST_PROMPT_PENDING')) {
|
||||
// Trust prompt appeared but couldn't be auto-approved
|
||||
res.status(200).json({
|
||||
error: 'Trust prompt pending',
|
||||
message:
|
||||
'Claude CLI needs folder permission. Please run "claude" in your terminal and approve access.',
|
||||
});
|
||||
} else if (message.includes('timed out')) {
|
||||
res.status(200).json({
|
||||
error: 'Command timed out',
|
||||
|
||||
@@ -49,13 +49,11 @@ export class ClaudeUsageService {
|
||||
|
||||
/**
|
||||
* Execute the claude /usage command and return the output
|
||||
* Uses platform-specific PTY implementation
|
||||
* Uses node-pty on all platforms for consistency
|
||||
*/
|
||||
private executeClaudeUsageCommand(): Promise<string> {
|
||||
if (this.isWindows || this.isLinux) {
|
||||
return this.executeClaudeUsageCommandPty();
|
||||
}
|
||||
return this.executeClaudeUsageCommandMac();
|
||||
// Use node-pty on all platforms - it's more reliable than expect on macOS
|
||||
return this.executeClaudeUsageCommandPty();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,24 +65,36 @@ export class ClaudeUsageService {
|
||||
let stderr = '';
|
||||
let settled = false;
|
||||
|
||||
// Use a simple working directory (home or tmp)
|
||||
const workingDirectory = process.env.HOME || '/tmp';
|
||||
// Use current working directory - likely already trusted by Claude CLI
|
||||
const workingDirectory = process.cwd();
|
||||
|
||||
// Use 'expect' with an inline script to run claude /usage with a PTY
|
||||
// Wait for "Current session" header, then wait for full output before exiting
|
||||
// Running from cwd which should already be trusted
|
||||
const expectScript = `
|
||||
set timeout 20
|
||||
set timeout 30
|
||||
spawn claude /usage
|
||||
|
||||
# Wait for usage data or handle trust prompt if needed
|
||||
expect {
|
||||
"Current session" {
|
||||
sleep 2
|
||||
send "\\x1b"
|
||||
-re "Ready to code|permission to work|Do you want to work" {
|
||||
# Trust prompt appeared - send Enter to approve
|
||||
sleep 1
|
||||
send "\\r"
|
||||
exp_continue
|
||||
}
|
||||
"Esc to cancel" {
|
||||
"Current session" {
|
||||
# Usage data appeared - wait for full output, then exit
|
||||
sleep 3
|
||||
send "\\x1b"
|
||||
}
|
||||
timeout {}
|
||||
"% left" {
|
||||
# Usage percentage appeared
|
||||
sleep 3
|
||||
send "\\x1b"
|
||||
}
|
||||
timeout {
|
||||
send "\\x1b"
|
||||
}
|
||||
eof {}
|
||||
}
|
||||
expect eof
|
||||
@@ -158,10 +168,10 @@ export class ClaudeUsageService {
|
||||
let output = '';
|
||||
let settled = false;
|
||||
let hasSeenUsageData = false;
|
||||
let hasSeenTrustPrompt = false;
|
||||
|
||||
const workingDirectory = this.isWindows
|
||||
? process.env.USERPROFILE || os.homedir() || 'C:\\'
|
||||
: os.tmpdir();
|
||||
// Use current working directory (project dir) - most likely already trusted by Claude CLI
|
||||
const workingDirectory = process.cwd();
|
||||
|
||||
// Use platform-appropriate shell and command
|
||||
const shell = this.isWindows ? 'cmd.exe' : '/bin/sh';
|
||||
@@ -206,6 +216,13 @@ export class ClaudeUsageService {
|
||||
// Don't fail if we have data - return it instead
|
||||
if (output.includes('Current session')) {
|
||||
resolve(output);
|
||||
} else if (hasSeenTrustPrompt) {
|
||||
// Trust prompt was shown but we couldn't auto-approve it
|
||||
reject(
|
||||
new Error(
|
||||
'TRUST_PROMPT_PENDING: Claude CLI is waiting for folder permission. Please run "claude" in your terminal and approve access to continue.'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
@@ -269,10 +286,18 @@ export class ClaudeUsageService {
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Handle Trust Dialog: "Do you want to work in this folder?"
|
||||
// Since we are running in os.tmpdir(), it is safe to approve.
|
||||
if (!hasApprovedTrust && cleanOutput.includes('Do you want to work in this folder?')) {
|
||||
// Handle Trust Dialog - multiple variants:
|
||||
// - "Do you want to work in this folder?"
|
||||
// - "Ready to code here?" / "I'll need permission to work with your files"
|
||||
// Since we are running in cwd (project dir), it is safe to approve.
|
||||
if (
|
||||
!hasApprovedTrust &&
|
||||
(cleanOutput.includes('Do you want to work in this folder?') ||
|
||||
cleanOutput.includes('Ready to code here') ||
|
||||
cleanOutput.includes('permission to work with your files'))
|
||||
) {
|
||||
hasApprovedTrust = true;
|
||||
hasSeenTrustPrompt = true;
|
||||
// Wait a tiny bit to ensure prompt is ready, then send Enter
|
||||
setTimeout(() => {
|
||||
if (!settled && ptyProcess && !ptyProcess.killed) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useSetupStore } from '@/store/setup-store';
|
||||
const ERROR_CODES = {
|
||||
API_BRIDGE_UNAVAILABLE: 'API_BRIDGE_UNAVAILABLE',
|
||||
AUTH_ERROR: 'AUTH_ERROR',
|
||||
TRUST_PROMPT: 'TRUST_PROMPT',
|
||||
UNKNOWN: 'UNKNOWN',
|
||||
} as const;
|
||||
|
||||
@@ -55,8 +56,12 @@ export function ClaudeUsagePopover() {
|
||||
}
|
||||
const data = await api.claude.getUsage();
|
||||
if ('error' in data) {
|
||||
// Detect trust prompt error
|
||||
const isTrustPrompt =
|
||||
data.error === 'Trust prompt pending' ||
|
||||
(data.message && data.message.includes('folder permission'));
|
||||
setError({
|
||||
code: ERROR_CODES.AUTH_ERROR,
|
||||
code: isTrustPrompt ? ERROR_CODES.TRUST_PROMPT : ERROR_CODES.AUTH_ERROR,
|
||||
message: data.message || data.error,
|
||||
});
|
||||
return;
|
||||
@@ -257,6 +262,11 @@ export function ClaudeUsagePopover() {
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{error.code === ERROR_CODES.API_BRIDGE_UNAVAILABLE ? (
|
||||
'Ensure the Electron bridge is running or restart the app'
|
||||
) : error.code === ERROR_CODES.TRUST_PROMPT ? (
|
||||
<>
|
||||
Run <code className="font-mono bg-muted px-1 rounded">claude</code> in your
|
||||
terminal and approve access to continue
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Make sure Claude CLI is installed and authenticated via{' '}
|
||||
|
||||
@@ -14,6 +14,7 @@ const ERROR_CODES = {
|
||||
API_BRIDGE_UNAVAILABLE: 'API_BRIDGE_UNAVAILABLE',
|
||||
AUTH_ERROR: 'AUTH_ERROR',
|
||||
NOT_AVAILABLE: 'NOT_AVAILABLE',
|
||||
TRUST_PROMPT: 'TRUST_PROMPT',
|
||||
UNKNOWN: 'UNKNOWN',
|
||||
} as const;
|
||||
|
||||
@@ -108,8 +109,12 @@ export function UsagePopover() {
|
||||
}
|
||||
const data = await api.claude.getUsage();
|
||||
if ('error' in data) {
|
||||
// Detect trust prompt error
|
||||
const isTrustPrompt =
|
||||
data.error === 'Trust prompt pending' ||
|
||||
(data.message && data.message.includes('folder permission'));
|
||||
setClaudeError({
|
||||
code: ERROR_CODES.AUTH_ERROR,
|
||||
code: isTrustPrompt ? ERROR_CODES.TRUST_PROMPT : ERROR_CODES.AUTH_ERROR,
|
||||
message: data.message || data.error,
|
||||
});
|
||||
return;
|
||||
@@ -404,6 +409,11 @@ export function UsagePopover() {
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{claudeError.code === ERROR_CODES.API_BRIDGE_UNAVAILABLE ? (
|
||||
'Ensure the Electron bridge is running or restart the app'
|
||||
) : claudeError.code === ERROR_CODES.TRUST_PROMPT ? (
|
||||
<>
|
||||
Run <code className="font-mono bg-muted px-1 rounded">claude</code> in
|
||||
your terminal and approve access to continue
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Make sure Claude CLI is installed and authenticated via{' '}
|
||||
|
||||
Reference in New Issue
Block a user