Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"github.vscode-github-actions"
"github.vscode-github-actions",
"vitest.explorer"
]
}
6 changes: 6 additions & 0 deletions packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# mycoder

## 0.2.3

### Patch Changes

- Ensure all logging follows logLevel cli option.

## 0.2.2

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "mycoder",
"description": "A command line tool using agent that can do arbitrary tasks, including coding tasks",
"version": "0.2.2",
"version": "0.2.3",
"type": "module",
"bin": "./bin/cli.js",
"main": "./dist/index.js",
Expand Down
14 changes: 11 additions & 3 deletions packages/cli/src/commands/$default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@ import { TokenTracker } from 'mycoder-agent/dist/core/tokens.js';

import { SharedOptions } from '../options.js';
import { hasUserConsented, saveUserConsent } from '../settings/settings.js';
import { getPackageInfo } from '../utils/versionCheck.js';
import { nameToLogIndex } from '../utils/nameToLogIndex.js';
import { checkForUpdates, getPackageInfo } from '../utils/versionCheck.js';

import type { CommandModule, Argv } from 'yargs';

interface DefaultArgs extends SharedOptions {
prompt?: string;
}

export const command: CommandModule<object, DefaultArgs> = {
export const command: CommandModule<SharedOptions, DefaultArgs> = {
command: '* [prompt]',
describe: 'Execute a prompt or start interactive mode',
builder: (yargs: Argv<object>): Argv<DefaultArgs> => {
Expand All @@ -32,12 +33,19 @@ export const command: CommandModule<object, DefaultArgs> = {
}) as Argv<DefaultArgs>;
},
handler: async (argv) => {
const logger = new Logger({ name: 'Default' });
const logger = new Logger({
name: 'Default',
logLevel: nameToLogIndex(argv.logLevel),
});

const packageInfo = getPackageInfo();

logger.info(
`MyCoder v${packageInfo.version} - AI-powered coding assistant`,
);

await checkForUpdates(logger);

if (!hasUserConsented()) {
const readline = createInterface({
input: process.stdin,
Expand Down
39 changes: 4 additions & 35 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,22 @@ import { join } from 'path';
import { fileURLToPath } from 'url';

import * as dotenv from 'dotenv';
import { Logger, LogLevel, getTools } from 'mycoder-agent';
import sourceMapSupport from 'source-map-support';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { fileCommands } from 'yargs-file-commands';

import { sharedOptions } from './options.js';
import { checkForUpdates } from './utils/versionCheck.js';

import type { PackageJson } from 'type-fest';

// Add global declaration for our patched toolAgent

sourceMapSupport.install();

const nameToLogIndex = (logLevelName: string) => {
// look up the log level name in the enum to get the value
return LogLevel[logLevelName as keyof typeof LogLevel];
};

const main = async () => {
dotenv.config();

const logger = new Logger({ name: 'Main' });

const updateMessage = await checkForUpdates();
if (updateMessage) {
console.log();
logger.info(updateMessage);
console.log();
}

process.on('uncaughtException', (error) => {
logger.error(
'Fatal error:',
error.constructor.name,
error.message,
error.stack,
);
process.exit(1);
});

const require = createRequire(import.meta.url);
const packageInfo = require('../package.json') as PackageJson;

Expand All @@ -59,14 +33,6 @@ const main = async () => {
.options(sharedOptions)
.alias('h', 'help')
.alias('V', 'version')
.middleware((argv) => {
// Set up logger with the specified log level
argv.logger = new Logger({
name: packageInfo.name!,
logLevel: nameToLogIndex(argv.log),
});
argv.tools = getTools();
})
.command(
await fileCommands({
commandDirs: [commandsDir],
Expand All @@ -78,4 +44,7 @@ const main = async () => {
.help().argv;
};

await main();
await main().catch((error) => {
console.error(error);
process.exit(1);
});
6 changes: 2 additions & 4 deletions packages/cli/src/options.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { LogLevel } from 'mycoder-agent';

export type SharedOptions = {
readonly log: LogLevel;
readonly logLevel: string;
readonly interactive: boolean;
readonly file?: string;
readonly tokenUsage?: boolean;
readonly headless?: boolean;
};

export const sharedOptions = {
log: {
logLevel: {
type: 'string',
alias: 'l',
description: 'Set minimum logging level',
Expand Down
6 changes: 6 additions & 0 deletions packages/cli/src/utils/nameToLogIndex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { LogLevel } from 'mycoder-agent';

export const nameToLogIndex = (logLevelName: string) => {
// look up the log level name in the enum to get the value
return LogLevel[logLevelName as keyof typeof LogLevel];
};
20 changes: 6 additions & 14 deletions packages/cli/src/utils/versionCheck.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as fs from 'fs';
import * as fsPromises from 'fs/promises';
import * as path from 'path';

import { MockLogger } from 'mycoder-agent';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';

import {
Expand All @@ -22,12 +23,6 @@ import { getSettingsDir } from '../settings/settings.js';

vi.mock('fs');
vi.mock('fs/promises');
vi.mock('mycoder-agent', () => ({
Logger: vi.fn().mockImplementation(() => ({
warn: vi.fn(),
})),
errorToString: vi.fn(),
}));

describe('versionCheck', () => {
describe('generateUpgradeMessage', () => {
Expand Down Expand Up @@ -139,8 +134,7 @@ describe('versionCheck', () => {
json: () => Promise.resolve({ version: '2.0.0' }),
});

const result = await checkForUpdates();
expect(result).toBe(null);
await checkForUpdates(new MockLogger());

// Wait for setImmediate to complete
await new Promise((resolve) => setImmediate(resolve));
Expand All @@ -161,8 +155,8 @@ describe('versionCheck', () => {
fsPromises.readFile as unknown as ReturnType<typeof vi.fn>
).mockResolvedValue('2.0.0');

const result = await checkForUpdates();
expect(result).toContain('Update available');
await checkForUpdates(new MockLogger());
// FIX: expect(result).toContain('Update available');
});

it('handles errors gracefully during version check', async () => {
Expand All @@ -173,8 +167,7 @@ describe('versionCheck', () => {
fsPromises.readFile as unknown as ReturnType<typeof vi.fn>
).mockRejectedValue(new Error('Test error'));

const result = await checkForUpdates();
expect(result).toBe(null);
await checkForUpdates(new MockLogger());
});

it('handles errors gracefully during background update', async () => {
Expand All @@ -183,8 +176,7 @@ describe('versionCheck', () => {
);
mockFetch.mockRejectedValue(new Error('Network error'));

const result = await checkForUpdates();
expect(result).toBe(null);
await checkForUpdates(new MockLogger());

// Wait for setImmediate to complete
await new Promise((resolve) => setImmediate(resolve));
Expand Down
20 changes: 10 additions & 10 deletions packages/cli/src/utils/versionCheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { getSettingsDir } from '../settings/settings.js';
import type { PackageJson } from 'type-fest';

const require = createRequire(import.meta.url);
const logger = new Logger({ name: 'version-check' });

export function getPackageInfo(): {
name: string;
Expand Down Expand Up @@ -56,18 +55,19 @@ export function generateUpgradeMessage(
: null;
}

export async function checkForUpdates(): Promise<string | null> {
export async function checkForUpdates(logger: Logger) {
try {
const { name: packageName, version: currentVersion } = getPackageInfo();

console.log('packageName', packageName);
console.log('currentVersion', currentVersion);
logger.debug(`checkForUpdates: currentVersion: ${currentVersion}`);

const settingDir = getSettingsDir();
const versionFilePath = path.join(settingDir, 'lastVersionCheck');
logger.debug(`checkForUpdates: versionFilePath: ${versionFilePath}`);

fetchLatestVersion(packageName)
.then(async (latestVersion) => {
console.log('latestVersion', latestVersion);
logger.debug(`checkForUpdates: latestVersion: ${latestVersion}`);
return fsPromises.writeFile(versionFilePath, latestVersion, 'utf8');
})
.catch((error) => {
Expand All @@ -79,18 +79,18 @@ export async function checkForUpdates(): Promise<string | null> {
versionFilePath,
'utf8',
);
console.log('lastVersionCheck', lastVersionCheck);
return generateUpgradeMessage(
logger.debug(`checkForUpdates: lastVersionCheck: ${lastVersionCheck}`);
const updateMessage = generateUpgradeMessage(
currentVersion,
lastVersionCheck,
packageName,
);
if (updateMessage) {
logger.info('\n' + updateMessage + '\n');
}
}

return null;
} catch (error) {
// Log error but don't throw to handle gracefully
logger.warn('Error checking for updates:', errorToString(error));
return null;
}
}
Loading