Adds commander dependency to @mosaicstack/log, @mosaicstack/memory, and @mosaicstack/queue so their CLI surface files typecheck correctly in the monorepo pre-push hook. Stages accompanying cli.ts and cli.spec.ts files written by parallel agents for the cli-unification-20260404 mission. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
178 lines
6.9 KiB
TypeScript
178 lines
6.9 KiB
TypeScript
import { writeFileSync } from 'node:fs';
|
|
|
|
import type { Command } from 'commander';
|
|
|
|
import type { LogCategory, LogLevel, LogTier } from './agent-logs.js';
|
|
|
|
interface FilterOptions {
|
|
agent?: string;
|
|
level?: string;
|
|
category?: string;
|
|
tier?: string;
|
|
limit?: string;
|
|
db?: string;
|
|
}
|
|
|
|
function parseLimit(raw: string | undefined, defaultVal = 50): number {
|
|
if (!raw) return defaultVal;
|
|
const n = parseInt(raw, 10);
|
|
return Number.isFinite(n) && n > 0 ? n : defaultVal;
|
|
}
|
|
|
|
function buildQuery(opts: FilterOptions) {
|
|
return {
|
|
...(opts.agent ? { sessionId: opts.agent } : {}),
|
|
...(opts.level ? { level: opts.level as LogLevel } : {}),
|
|
...(opts.category ? { category: opts.category as LogCategory } : {}),
|
|
...(opts.tier ? { tier: opts.tier as LogTier } : {}),
|
|
limit: parseLimit(opts.limit),
|
|
};
|
|
}
|
|
|
|
async function openDb(connectionString: string) {
|
|
const { createDb } = await import('@mosaicstack/db');
|
|
return createDb(connectionString);
|
|
}
|
|
|
|
function resolveConnectionString(opts: FilterOptions): string | undefined {
|
|
return opts.db ?? process.env['DATABASE_URL'];
|
|
}
|
|
|
|
/**
|
|
* Register log subcommands on an existing Commander program.
|
|
* This avoids cross-package Commander version mismatches by using the
|
|
* caller's Command instance directly.
|
|
*/
|
|
export function registerLogCommand(parent: Command): void {
|
|
const log = parent.command('log').description('Query and manage agent logs');
|
|
|
|
// ─── tail ───────────────────────────────────────────────────────────────
|
|
|
|
log
|
|
.command('tail')
|
|
.description('Tail recent agent logs')
|
|
.option('--agent <id>', 'Filter by agent/session ID')
|
|
.option('--level <level>', 'Filter by log level (debug|info|warn|error)')
|
|
.option('--category <cat>', 'Filter by category (decision|tool_use|learning|error|general)')
|
|
.option('--tier <tier>', 'Filter by tier (hot|warm|cold)')
|
|
.option('--limit <n>', 'Number of logs to return (default 50)', '50')
|
|
.option('--db <connection-string>', 'Database connection string (or set DATABASE_URL)')
|
|
.action(async (opts: FilterOptions) => {
|
|
const connStr = resolveConnectionString(opts);
|
|
if (!connStr) {
|
|
console.error('Database connection required: use --db or set DATABASE_URL');
|
|
process.exit(1);
|
|
}
|
|
|
|
const handle = await openDb(connStr);
|
|
try {
|
|
const { createLogService } = await import('./log-service.js');
|
|
const svc = createLogService(handle.db);
|
|
const query = buildQuery(opts);
|
|
|
|
const logs = await svc.logs.query(query);
|
|
if (logs.length === 0) {
|
|
console.log('No logs found.');
|
|
return;
|
|
}
|
|
for (const entry of logs) {
|
|
const ts = new Date(entry.createdAt).toISOString();
|
|
console.log(`[${ts}] [${entry.level}] [${entry.category}] ${entry.content}`);
|
|
}
|
|
} finally {
|
|
await handle.close();
|
|
}
|
|
});
|
|
|
|
// ─── search ─────────────────────────────────────────────────────────────
|
|
|
|
log
|
|
.command('search <query>')
|
|
.description('Full-text search over agent logs')
|
|
.option('--agent <id>', 'Filter by agent/session ID')
|
|
.option('--level <level>', 'Filter by log level (debug|info|warn|error)')
|
|
.option('--category <cat>', 'Filter by category (decision|tool_use|learning|error|general)')
|
|
.option('--tier <tier>', 'Filter by tier (hot|warm|cold)')
|
|
.option('--limit <n>', 'Number of logs to return (default 50)', '50')
|
|
.option('--db <connection-string>', 'Database connection string (or set DATABASE_URL)')
|
|
.action(async (query: string, opts: FilterOptions) => {
|
|
const connStr = resolveConnectionString(opts);
|
|
if (!connStr) {
|
|
console.error('Database connection required: use --db or set DATABASE_URL');
|
|
process.exit(1);
|
|
}
|
|
|
|
const handle = await openDb(connStr);
|
|
try {
|
|
const { createLogService } = await import('./log-service.js');
|
|
const svc = createLogService(handle.db);
|
|
const baseQuery = buildQuery(opts);
|
|
|
|
const logs = await svc.logs.query(baseQuery);
|
|
const lowerQ = query.toLowerCase();
|
|
const matched = logs.filter(
|
|
(e) =>
|
|
e.content.toLowerCase().includes(lowerQ) ||
|
|
(e.metadata != null && JSON.stringify(e.metadata).toLowerCase().includes(lowerQ)),
|
|
);
|
|
|
|
if (matched.length === 0) {
|
|
console.log('No matching logs found.');
|
|
return;
|
|
}
|
|
for (const entry of matched) {
|
|
const ts = new Date(entry.createdAt).toISOString();
|
|
console.log(`[${ts}] [${entry.level}] [${entry.category}] ${entry.content}`);
|
|
}
|
|
} finally {
|
|
await handle.close();
|
|
}
|
|
});
|
|
|
|
// ─── export ─────────────────────────────────────────────────────────────
|
|
|
|
log
|
|
.command('export <path>')
|
|
.description('Export matching logs to an NDJSON file')
|
|
.option('--agent <id>', 'Filter by agent/session ID')
|
|
.option('--level <level>', 'Filter by log level (debug|info|warn|error)')
|
|
.option('--category <cat>', 'Filter by category (decision|tool_use|learning|error|general)')
|
|
.option('--tier <tier>', 'Filter by tier (hot|warm|cold)')
|
|
.option('--limit <n>', 'Number of logs to export (default 50)', '50')
|
|
.option('--db <connection-string>', 'Database connection string (or set DATABASE_URL)')
|
|
.action(async (outputPath: string, opts: FilterOptions) => {
|
|
const connStr = resolveConnectionString(opts);
|
|
if (!connStr) {
|
|
console.error('Database connection required: use --db or set DATABASE_URL');
|
|
process.exit(1);
|
|
}
|
|
|
|
const handle = await openDb(connStr);
|
|
try {
|
|
const { createLogService } = await import('./log-service.js');
|
|
const svc = createLogService(handle.db);
|
|
const query = buildQuery(opts);
|
|
|
|
const logs = await svc.logs.query(query);
|
|
const ndjson = logs.map((e) => JSON.stringify(e)).join('\n');
|
|
writeFileSync(outputPath, ndjson, 'utf8');
|
|
console.log(`Exported ${logs.length} log(s) to ${outputPath}`);
|
|
} finally {
|
|
await handle.close();
|
|
}
|
|
});
|
|
|
|
// ─── level ──────────────────────────────────────────────────────────────
|
|
|
|
log
|
|
.command('level <level>')
|
|
.description('Set runtime log level for the connected log service')
|
|
.action((level: string) => {
|
|
void level;
|
|
console.log(
|
|
'Runtime log level adjustment is not supported in current mode (DB-backed log service).',
|
|
);
|
|
process.exitCode = 0;
|
|
});
|
|
}
|