242 lines
7.6 KiB
TypeScript
242 lines
7.6 KiB
TypeScript
import type { Command } from 'commander';
|
|
import { withAuth } from './with-auth.js';
|
|
import { selectItem } from './select-dialog.js';
|
|
import {
|
|
fetchAgentConfigs,
|
|
createAgentConfig,
|
|
updateAgentConfig,
|
|
deleteAgentConfig,
|
|
fetchProjects,
|
|
fetchProviders,
|
|
} from '../tui/gateway-api.js';
|
|
import type { AgentConfigInfo } from '../tui/gateway-api.js';
|
|
|
|
function formatAgent(a: AgentConfigInfo): string {
|
|
const sys = a.isSystem ? ' [system]' : '';
|
|
return `${a.name}${sys} — ${a.provider}/${a.model} (${a.status})`;
|
|
}
|
|
|
|
function showAgentDetail(a: AgentConfigInfo) {
|
|
console.log(` ID: ${a.id}`);
|
|
console.log(` Name: ${a.name}`);
|
|
console.log(` Provider: ${a.provider}`);
|
|
console.log(` Model: ${a.model}`);
|
|
console.log(` Status: ${a.status}`);
|
|
console.log(` System: ${a.isSystem ? 'yes' : 'no'}`);
|
|
console.log(` Project: ${a.projectId ?? '—'}`);
|
|
console.log(` System Prompt: ${a.systemPrompt ? `${a.systemPrompt.slice(0, 80)}...` : '—'}`);
|
|
console.log(` Tools: ${a.allowedTools ? a.allowedTools.join(', ') : 'all'}`);
|
|
console.log(` Skills: ${a.skills ? a.skills.join(', ') : '—'}`);
|
|
console.log(` Created: ${new Date(a.createdAt).toLocaleString()}`);
|
|
}
|
|
|
|
export function registerAgentCommand(program: Command) {
|
|
const cmd = program
|
|
.command('agent')
|
|
.description('Manage agent configurations')
|
|
.option('-g, --gateway <url>', 'Gateway URL', 'http://localhost:14242')
|
|
.option('--list', 'List all agents')
|
|
.option('--new', 'Create a new agent')
|
|
.option('--show <idOrName>', 'Show agent details')
|
|
.option('--update <idOrName>', 'Update an agent')
|
|
.option('--delete <idOrName>', 'Delete an agent')
|
|
.action(
|
|
async (opts: {
|
|
gateway: string;
|
|
list?: boolean;
|
|
new?: boolean;
|
|
show?: string;
|
|
update?: string;
|
|
delete?: string;
|
|
}) => {
|
|
const auth = await withAuth(opts.gateway);
|
|
|
|
if (opts.list) {
|
|
return listAgents(auth.gateway, auth.cookie);
|
|
}
|
|
if (opts.new) {
|
|
return createAgentWizard(auth.gateway, auth.cookie);
|
|
}
|
|
if (opts.show) {
|
|
return showAgent(auth.gateway, auth.cookie, opts.show);
|
|
}
|
|
if (opts.update) {
|
|
return updateAgentWizard(auth.gateway, auth.cookie, opts.update);
|
|
}
|
|
if (opts.delete) {
|
|
return deleteAgent(auth.gateway, auth.cookie, opts.delete);
|
|
}
|
|
|
|
// Default: interactive select
|
|
return interactiveSelect(auth.gateway, auth.cookie);
|
|
},
|
|
);
|
|
|
|
return cmd;
|
|
}
|
|
|
|
async function resolveAgent(
|
|
gateway: string,
|
|
cookie: string,
|
|
idOrName: string,
|
|
): Promise<AgentConfigInfo | undefined> {
|
|
const agents = await fetchAgentConfigs(gateway, cookie);
|
|
return agents.find((a) => a.id === idOrName || a.name === idOrName);
|
|
}
|
|
|
|
async function listAgents(gateway: string, cookie: string) {
|
|
const agents = await fetchAgentConfigs(gateway, cookie);
|
|
if (agents.length === 0) {
|
|
console.log('No agents found.');
|
|
return;
|
|
}
|
|
console.log(`Agents (${agents.length}):\n`);
|
|
for (const a of agents) {
|
|
const sys = a.isSystem ? ' [system]' : '';
|
|
const project = a.projectId ? ` project=${a.projectId.slice(0, 8)}` : '';
|
|
console.log(` ${a.name}${sys} ${a.provider}/${a.model} ${a.status}${project}`);
|
|
}
|
|
}
|
|
|
|
async function showAgent(gateway: string, cookie: string, idOrName: string) {
|
|
const agent = await resolveAgent(gateway, cookie, idOrName);
|
|
if (!agent) {
|
|
console.error(`Agent "${idOrName}" not found.`);
|
|
process.exit(1);
|
|
}
|
|
showAgentDetail(agent);
|
|
}
|
|
|
|
async function interactiveSelect(gateway: string, cookie: string) {
|
|
const agents = await fetchAgentConfigs(gateway, cookie);
|
|
const selected = await selectItem(agents, {
|
|
message: 'Select an agent:',
|
|
render: formatAgent,
|
|
emptyMessage: 'No agents found. Create one with `mosaic agent --new`.',
|
|
});
|
|
if (selected) {
|
|
showAgentDetail(selected);
|
|
}
|
|
}
|
|
|
|
async function createAgentWizard(gateway: string, cookie: string) {
|
|
const readline = await import('node:readline');
|
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
const ask = (q: string): Promise<string> => new Promise((resolve) => rl.question(q, resolve));
|
|
|
|
try {
|
|
const name = await ask('Agent name: ');
|
|
if (!name.trim()) {
|
|
console.error('Name is required.');
|
|
return;
|
|
}
|
|
|
|
// Project selection
|
|
const projects = await fetchProjects(gateway, cookie);
|
|
let projectId: string | undefined;
|
|
if (projects.length > 0) {
|
|
const selected = await selectItem(projects, {
|
|
message: 'Assign to project (optional):',
|
|
render: (p) => `${p.name} (${p.status})`,
|
|
});
|
|
if (selected) projectId = selected.id;
|
|
}
|
|
|
|
// Provider / model selection
|
|
const providers = await fetchProviders(gateway, cookie);
|
|
let provider = 'default';
|
|
let model = 'default';
|
|
|
|
if (providers.length > 0) {
|
|
const allModels = providers.flatMap((p) =>
|
|
p.models.map((m) => ({ provider: p.name, model: m.id, label: `${p.name}/${m.id}` })),
|
|
);
|
|
if (allModels.length > 0) {
|
|
const selected = await selectItem(allModels, {
|
|
message: 'Select model:',
|
|
render: (m) => m.label,
|
|
});
|
|
if (selected) {
|
|
provider = selected.provider;
|
|
model = selected.model;
|
|
}
|
|
}
|
|
}
|
|
|
|
const systemPrompt = await ask('System prompt (optional, press Enter to skip): ');
|
|
|
|
const agent = await createAgentConfig(gateway, cookie, {
|
|
name: name.trim(),
|
|
provider,
|
|
model,
|
|
projectId,
|
|
systemPrompt: systemPrompt.trim() || undefined,
|
|
});
|
|
|
|
console.log(`\nAgent "${agent.name}" created (${agent.id}).`);
|
|
} finally {
|
|
rl.close();
|
|
}
|
|
}
|
|
|
|
async function updateAgentWizard(gateway: string, cookie: string, idOrName: string) {
|
|
const agent = await resolveAgent(gateway, cookie, idOrName);
|
|
if (!agent) {
|
|
console.error(`Agent "${idOrName}" not found.`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const readline = await import('node:readline');
|
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
const ask = (q: string): Promise<string> => new Promise((resolve) => rl.question(q, resolve));
|
|
|
|
try {
|
|
console.log(`Updating agent: ${agent.name}\n`);
|
|
|
|
const name = await ask(`Name [${agent.name}]: `);
|
|
const systemPrompt = await ask(`System prompt [${agent.systemPrompt ? 'set' : 'none'}]: `);
|
|
|
|
const updates: Record<string, unknown> = {};
|
|
if (name.trim()) updates['name'] = name.trim();
|
|
if (systemPrompt.trim()) updates['systemPrompt'] = systemPrompt.trim();
|
|
|
|
if (Object.keys(updates).length === 0) {
|
|
console.log('No changes.');
|
|
return;
|
|
}
|
|
|
|
const updated = await updateAgentConfig(gateway, cookie, agent.id, updates);
|
|
console.log(`\nAgent "${updated.name}" updated.`);
|
|
} finally {
|
|
rl.close();
|
|
}
|
|
}
|
|
|
|
async function deleteAgent(gateway: string, cookie: string, idOrName: string) {
|
|
const agent = await resolveAgent(gateway, cookie, idOrName);
|
|
if (!agent) {
|
|
console.error(`Agent "${idOrName}" not found.`);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (agent.isSystem) {
|
|
console.error('Cannot delete system agents.');
|
|
process.exit(1);
|
|
}
|
|
|
|
const readline = await import('node:readline');
|
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
const answer = await new Promise<string>((resolve) =>
|
|
rl.question(`Delete agent "${agent.name}"? (y/N): `, resolve),
|
|
);
|
|
rl.close();
|
|
|
|
if (answer.toLowerCase() !== 'y') {
|
|
console.log('Cancelled.');
|
|
return;
|
|
}
|
|
|
|
await deleteAgentConfig(gateway, cookie, agent.id);
|
|
console.log(`Agent "${agent.name}" deleted.`);
|
|
}
|