feat(cli): personality flag + create/get/edit/delete personalities (Stage 4)
End-to-end CLI surface for the personality overlay: mcpctl create personality grumpy --agent reviewer --description "be terse" mcpctl create prompt tone --agent reviewer --content "Be very terse." mcpctl get personalities mcpctl get personalities --agent reviewer mcpctl edit personality <id> mcpctl delete personality grumpy --agent reviewer mcpctl chat reviewer --personality grumpy Chat banner gains a "Personality:" line that shows either the active flag value or the agent's `defaultPersonality` (when no flag given), so the user knows which overlay is in effect before sending a message. `--personality` is stripped from `/save` (it's a per-turn override, not a `defaultParams` field — the agent's defaultPersonality lives on its own column and is set via PUT /agents). Backend (small additions to land Stage 4 cleanly): - `GET /api/v1/personalities[?agent=name]` so `mcpctl get personalities` doesn't require an agent filter. - PersonalityService.listAll() aggregates across agents. Completions: regenerated fish + bash. `personalities` added as a canonical resource with `personality` alias; edit-resource list extended; the per-resource argument completers pick up the new type automatically. CLI suite: 430/430. mcpd: 801/801. Typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,24 @@ export function registerPersonalityRoutes(
|
||||
app: FastifyInstance,
|
||||
service: PersonalityService,
|
||||
): void {
|
||||
app.get<{ Querystring: { agent?: string } }>(
|
||||
'/api/v1/personalities',
|
||||
async (request, reply) => {
|
||||
try {
|
||||
if (request.query.agent !== undefined) {
|
||||
return await service.listForAgent(request.query.agent);
|
||||
}
|
||||
return await service.listAll();
|
||||
} catch (err) {
|
||||
if (err instanceof NotFoundError) {
|
||||
reply.code(404);
|
||||
return { error: err.message };
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
app.get<{ Params: { agentName: string } }>(
|
||||
'/api/v1/agents/:agentName/personalities',
|
||||
async (request, reply) => {
|
||||
|
||||
@@ -54,6 +54,18 @@ export class PersonalityService {
|
||||
private readonly promptRepo: IPromptRepository,
|
||||
) {}
|
||||
|
||||
async listAll(): Promise<PersonalityView[]> {
|
||||
const rows = await this.repo.findAll();
|
||||
const agents = new Map<string, string>();
|
||||
for (const r of rows) {
|
||||
if (!agents.has(r.agentId)) {
|
||||
const agent = await this.agentRepo.findById(r.agentId);
|
||||
agents.set(r.agentId, agent?.name ?? r.agentId);
|
||||
}
|
||||
}
|
||||
return Promise.all(rows.map((r) => this.toView(r, agents.get(r.agentId) ?? r.agentId)));
|
||||
}
|
||||
|
||||
async listForAgent(agentName: string): Promise<PersonalityView[]> {
|
||||
const agent = await this.agentRepo.findByName(agentName);
|
||||
if (agent === null) throw new NotFoundError(`Agent not found: ${agentName}`);
|
||||
|
||||
Reference in New Issue
Block a user