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>
90 lines
3.7 KiB
TypeScript
90 lines
3.7 KiB
TypeScript
import { Command } from 'commander';
|
|
import type { ApiClient } from '../api-client.js';
|
|
import { resolveResource, resolveNameOrId } from './shared.js';
|
|
|
|
export interface DeleteCommandDeps {
|
|
client: ApiClient;
|
|
log: (...args: unknown[]) => void;
|
|
}
|
|
|
|
export function createDeleteCommand(deps: DeleteCommandDeps): Command {
|
|
const { client, log } = deps;
|
|
|
|
return new Command('delete')
|
|
.description('Delete a resource (server, instance, secret, project, user, group, rbac, personality)')
|
|
.argument('<resource>', 'resource type')
|
|
.argument('<id>', 'resource ID or name')
|
|
.option('-p, --project <name>', 'Project name (for serverattachment)')
|
|
.option('--agent <name>', 'Agent name (for personality delete-by-name)')
|
|
.action(async (resourceArg: string, idOrName: string, opts: { project?: string; agent?: string }) => {
|
|
const resource = resolveResource(resourceArg);
|
|
|
|
// Serverattachments: delete serverattachment <server> --project <project>
|
|
if (resource === 'serverattachments') {
|
|
if (!opts.project) {
|
|
throw new Error('--project is required. Usage: mcpctl delete serverattachment <server> --project <name>');
|
|
}
|
|
const projectId = await resolveNameOrId(client, 'projects', opts.project);
|
|
await client.delete(`/api/v1/projects/${projectId}/servers/${idOrName}`);
|
|
log(`server '${idOrName}' detached from project '${opts.project}'`);
|
|
return;
|
|
}
|
|
|
|
// Personalities: names are unique per-agent, so by-name delete requires --agent
|
|
// (or pass a CUID directly).
|
|
if (resource === 'personalities') {
|
|
let personalityId: string;
|
|
if (/^c[a-z0-9]{24}/.test(idOrName)) {
|
|
personalityId = idOrName;
|
|
} else {
|
|
if (!opts.agent) {
|
|
throw new Error('--agent is required to delete a personality by name (or pass the id).');
|
|
}
|
|
const items = await client.get<Array<{ id: string; name: string }>>(
|
|
`/api/v1/agents/${encodeURIComponent(opts.agent)}/personalities`,
|
|
);
|
|
const match = items.find((i) => i.name === idOrName);
|
|
if (!match) throw new Error(`personality '${idOrName}' not found on agent '${opts.agent}'`);
|
|
personalityId = match.id;
|
|
}
|
|
await client.delete(`/api/v1/personalities/${personalityId}`);
|
|
log(`personality '${idOrName}' deleted.`);
|
|
return;
|
|
}
|
|
|
|
// Mcptokens: names are scoped to a project, so require --project unless the caller passes a CUID
|
|
if (resource === 'mcptokens') {
|
|
let tokenId: string;
|
|
if (/^c[a-z0-9]{24}/.test(idOrName)) {
|
|
tokenId = idOrName;
|
|
} else {
|
|
if (!opts.project) {
|
|
throw new Error('--project is required to delete an mcptoken by name (or pass the id).');
|
|
}
|
|
const items = await client.get<Array<{ id: string; name: string }>>(
|
|
`/api/v1/mcptokens?projectName=${encodeURIComponent(opts.project)}`,
|
|
);
|
|
const match = items.find((i) => i.name === idOrName);
|
|
if (!match) throw new Error(`mcptoken '${idOrName}' not found in project '${opts.project}'`);
|
|
tokenId = match.id;
|
|
}
|
|
await client.delete(`/api/v1/mcptokens/${tokenId}`);
|
|
log(`mcptoken '${idOrName}' deleted.`);
|
|
return;
|
|
}
|
|
|
|
// Resolve name → ID for any resource type
|
|
let id: string;
|
|
try {
|
|
id = await resolveNameOrId(client, resource, idOrName);
|
|
} catch {
|
|
id = idOrName; // Fall through with original
|
|
}
|
|
|
|
await client.delete(`/api/v1/${resource}/${id}`);
|
|
|
|
const singular = resource.replace(/s$/, '');
|
|
log(`${singular} '${idOrName}' deleted.`);
|
|
});
|
|
}
|