Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ describe('resolveBudget', () => {
}));
const getAuthHeadersImpl = vi.fn().mockResolvedValue(null);
const result = await resolveBudget({ readFile, writeFile: vi.fn(), fetchImpl: vi.fn(), getAuthHeadersImpl });
expect(result).toEqual({ budget: null, budgetError: 'reauthenticate' });
expect(result).toEqual({ budget: null, budgetError: 'reauthenticate - run codemie profile login' });
});

it('returns the HTTP error message when the fetch fails', async () => {
Expand Down
36 changes: 26 additions & 10 deletions src/agents/plugins/claude/plugin/statusline.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,27 @@ const ENCRYPTION_KEY = (() => {
function decrypt(text) {
const parts = text.split(':');
if (parts.length === 3) {
const iv = Buffer.from(parts[0], 'hex');
const authTag = Buffer.from(parts[1], 'hex');
const d = crypto.createDecipheriv('aes-256-gcm', ENCRYPTION_KEY, iv);
d.setAuthTag(authTag);
return d.update(parts[2], 'hex', 'utf8') + d.final('utf8');
// Current GCM format: iv:authTag:encrypted
try {
const iv = Buffer.from(parts[0], 'hex');
const authTag = Buffer.from(parts[1], 'hex');
const d = crypto.createDecipheriv('aes-256-gcm', ENCRYPTION_KEY, iv);
d.setAuthTag(authTag);
return d.update(parts[2], 'hex', 'utf8') + d.final('utf8');
} catch (gcmError) {
// If GCM fails, try CBC as fallback for malformed data
console.error(`[CodeMie Statusline] GCM decryption failed, trying CBC fallback: ${gcmError.message}`);
}
}
// Legacy CBC format: iv:encrypted (backward compat for existing stored credentials)
const iv = Buffer.from(parts[0], 'hex');
const d = crypto.createDecipheriv('aes-256-cbc', ENCRYPTION_KEY, iv);
return d.update(parts[1], 'hex', 'utf8') + d.final('utf8');
try {
const iv = Buffer.from(parts[0], 'hex');
const d = crypto.createDecipheriv('aes-256-cbc', ENCRYPTION_KEY, iv);
return d.update(parts[1], 'hex', 'utf8') + d.final('utf8');
} catch (cbcError) {
console.error(`[CodeMie Statusline] CBC decryption failed: ${cbcError.message}`);
throw new Error('Failed to decrypt credentials - please re-authenticate with codemie profile login');
}
}

function urlHash(rawUrl) {
Expand Down Expand Up @@ -225,10 +236,15 @@ export async function resolveBudget({
try {
headers = await getAuthHeadersImpl(codeMieUrl);
} catch (e) {
return { budget: null, budgetError: e.message };
const errorMsg = e.message || String(e);
// Provide more specific error messages instead of generic "reauthenticate"
if (errorMsg.includes('decrypt') || errorMsg.includes('cipher') || errorMsg.includes('authentication')) {
return { budget: null, budgetError: 'auth error - try codemie profile login' };
}
return { budget: null, budgetError: errorMsg };
}
if (!headers) {
return { budget: null, budgetError: 'reauthenticate' };
return { budget: null, budgetError: 'reauthenticate - run codemie profile login' };
}

try {
Expand Down
18 changes: 17 additions & 1 deletion src/agents/plugins/claude/statusline-installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,23 @@ export async function installStatusline(): Promise<InstallStatuslineResult> {
}
}

const alreadyConfigured = Boolean(settings.statusLine);
// Check if there's an existing CodeMie statusline configuration that needs migration
const existingStatusLine = settings.statusLine as Record<string, unknown> | undefined;
let alreadyConfigured = false;

if (existingStatusLine) {
// Check if the existing statusLine points to a CodeMie script (old or new)
const command = String(existingStatusLine.command || '');
if (command.includes('codemie-budget-status.js') || command.includes('codemie-statusline.mjs')) {
// This is a CodeMie-managed statusline - migrate it to the current script
logger.info('[Statusline] Migrating existing CodeMie statusline configuration to current script');
alreadyConfigured = true;
} else {
// This is a non-CodeMie statusline - don't touch it
logger.debug('[Statusline] Existing statusLine is not CodeMie-managed, leaving unchanged');
return { scriptPath, alreadyConfigured: true };
}
}

settings.statusLine = {
type: 'command',
Expand Down
9 changes: 8 additions & 1 deletion src/migrations/__tests__/migration-runner-ordering.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,14 @@ afterEach(async () => {
openLoggers = [];
if (originalCodemieHome !== undefined) process.env.CODEMIE_HOME = originalCodemieHome;
else delete process.env.CODEMIE_HOME;
rmSync(tmpHome, { recursive: true, force: true });
// Windows can still hold log file handles briefly after stream.end(); force +
// maxRetries only suppress ENOENT, so treat residual ENOTEMPTY/EBUSY as
// best-effort teardown (matches claude.metrics-processor-names.test.ts).
try {
rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
} catch {
/* ignore temp-dir cleanup races */
}
vi.restoreAllMocks();
});

Expand Down
Loading