@agentled/cli
Version:
CLI for Agentled — manage workflows, apps, and knowledge from the command line. Zero context-window cost for AI agents.
464 lines • 21.3 kB
JavaScript
import { readFileSync } from 'node:fs';
import { AgentledClient } from '../client.js';
import { printError, printOutput } from '../utils/output.js';
function readJson(opts, label) {
if (!opts.input && !opts.file)
throw new Error(`Provide ${label} via --input '<json>' or --file <path>`);
if (opts.input && opts.file)
throw new Error(`Provide ${label} with either --input or --file, not both`);
const raw = opts.file ? readFileSync(opts.file, 'utf8') : opts.input;
return JSON.parse(raw);
}
function parseCsvList(value) {
if (value === undefined)
return undefined;
return value
.split(',')
.map((item) => item.trim())
.filter(Boolean);
}
function parseJsonOption(value, label) {
if (value === undefined)
return undefined;
try {
const parsed = JSON.parse(value);
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error(`${label} must be a JSON object`);
}
return parsed;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Invalid ${label}: ${message}`);
}
}
function readText(opts, label) {
if (opts.content === undefined && opts.file === undefined)
throw new Error(`Provide ${label} via --content '<text>' or --file <path>`);
if (opts.content !== undefined && opts.file !== undefined)
throw new Error(`Provide ${label} with either --content or --file, not both`);
return opts.file ? readFileSync(opts.file, 'utf8') : opts.content;
}
function buildWorkspaceSkillImportPayload(opts) {
if (!opts.file && !opts.url)
throw new Error('Provide a skill source via --file <path> or --url <url>');
if (opts.file && opts.url)
throw new Error('Provide a skill source with either --file or --url, not both');
if (opts.file) {
return {
content: readFileSync(opts.file, 'utf8'),
sourceLabel: opts.file,
};
}
return {
url: opts.url.trim(),
};
}
export function registerAgentCommands(program) {
const agents = program.command('agents').description('Manage agent entities');
agents
.command('list')
.description('List agents')
.option('--status <status>', 'Filter by status')
.option('--format <fmt>', 'Output format', 'json')
.action(async (opts) => {
try {
const result = await new AgentledClient().listAgents({ status: opts.status });
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
agents
.command('get <id>')
.description('Get an agent by id')
.option('--format <fmt>', 'Output format', 'json')
.action(async (id, opts) => {
try {
const result = await new AgentledClient().getAgent(id);
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
agents
.command('skills')
.description('List supported built-in agent skill IDs')
.option('--include-runtime', 'Include hidden runtime bundle IDs for advanced configuration')
.option('--format <fmt>', 'Output format', 'json')
.action(async (opts) => {
try {
const result = await new AgentledClient().listAgentSkills({ includeRuntime: opts.includeRuntime });
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
agents
.command('create')
.description('Create an agent')
.requiredOption('--name <name>', 'Agent name')
.option('--description <description>', 'Agent description')
.option('--instructions <instructions>', 'Agent instructions')
.option('--slug <slug>', 'Agent slug')
.option('--status <status>', 'Agent status')
.option('--model-tier <tier>', 'Model tier')
.option('--max-credits-per-day <n>', 'Daily credit limit', parseFloat)
.option('--skill-ids <ids>', 'Comma-separated runtime skill IDs, e.g. outcome-solver,workflow-manager')
.option('--enabled-skills <ids>', 'Comma-separated runtime skill IDs, e.g. outcome-solver,workflow-manager')
.option('--input <json>', 'Full agent JSON payload (overrides CLI flags)')
.option('--file <path>', 'Path to JSON payload file')
.option('--format <fmt>', 'Output format', 'json')
.action(async (opts) => {
try {
const payload = opts.input || opts.file
? readJson(opts, 'agent payload')
: {
name: opts.name,
description: opts.description,
instructions: opts.instructions,
slug: opts.slug,
status: opts.status,
modelTier: opts.modelTier,
maxCreditsPerDay: opts.maxCreditsPerDay,
enabledSkills: parseCsvList(opts.skillIds) ?? parseCsvList(opts.enabledSkills),
};
const result = await new AgentledClient().createAgent(payload);
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
agents
.command('update <id>')
.description('Update an agent')
.option('--input <json>', 'JSON updates object; cannot be combined with skill flags')
.option('--file <path>', 'Path to JSON updates object; cannot be combined with skill flags')
.option('--skill-ids <ids>', 'Comma-separated runtime skill IDs; cannot be combined with --input/--file; pass an empty string to disable inherited skills')
.option('--enabled-skills <ids>', 'Alias for --skill-ids; cannot be combined with --input/--file; pass an empty string to disable inherited skills')
.option('--format <fmt>', 'Output format', 'json')
.action(async (id, opts) => {
try {
const hasStructuredInput = opts.input !== undefined || opts.file !== undefined;
const hasSkillFlags = opts.skillIds !== undefined || opts.enabledSkills !== undefined;
if (hasStructuredInput && hasSkillFlags) {
throw new Error('Provide agent updates with either --input/--file or --skill-ids/--enabled-skills, not both');
}
const payload = opts.input || opts.file
? readJson(opts, 'agent updates')
: {
enabledSkills: parseCsvList(opts.skillIds) ?? parseCsvList(opts.enabledSkills),
};
if (Object.values(payload).every((value) => value === undefined)) {
throw new Error('Provide agent updates via --input, --file, --skill-ids, or --enabled-skills');
}
// The CLI passes the user's JSON through as-is. The route accepts
// both the surgical shape ({ updates, replace, unset }) and the
// legacy flat shape ({ name, slug, iconName, … }) — wrapping flat
// input here would lose legacy-only fields (slug, iconName, agentMode)
// by funneling them through the stricter surgical validator.
const result = await new AgentledClient().updateAgent(id, payload);
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
agents
.command('delete <id>')
.description('Delete an agent')
.option('--format <fmt>', 'Output format', 'json')
.action(async (id, opts) => {
try {
const result = await new AgentledClient().deleteAgent(id);
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
agents
.command('chat <id> <message>')
.description('Chat with an agent')
.option('--session-id <id>', 'Continue an existing session')
.option('--format <fmt>', 'Output format', 'json')
.action(async (id, message, opts) => {
try {
const result = await new AgentledClient().chatWithAgent(id, message, opts.sessionId);
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
const files = agents.command('files').description('Manage files attached to an agent');
files
.command('list <agent-id>')
.description('List files attached to an agent')
.option('--format <fmt>', 'Output format', 'json')
.action(async (agentId, opts) => {
try {
const result = await new AgentledClient().listAgentFiles(agentId);
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
files
.command('get <agent-id> <file-id>')
.description('Get a file attached to an agent')
.option('--format <fmt>', 'Output format', 'json')
.action(async (agentId, fileId, opts) => {
try {
const result = await new AgentledClient().getAgentFile(agentId, fileId);
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
files
.command('upload <agent-id>')
.description('Upload a new file and attach it to an agent')
.requiredOption('--name <name>', 'Filename')
.option('--content <text>', 'File content')
.option('--file <path>', 'Path to file content')
.option('--mime-type <type>', 'MIME/content type', 'text/plain')
.option('--role <role>', 'File role / purpose label')
.option('--format <fmt>', 'Output format', 'json')
.action(async (agentId, opts) => {
try {
const content = readText(opts, 'file content');
const result = await new AgentledClient().uploadAgentFile(agentId, {
name: opts.name,
content,
mimeType: opts.mimeType,
role: opts.role,
});
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
files
.command('update <agent-id> <file-id>')
.description('Update a file attached to an agent')
.option('--name <name>', 'New filename')
.option('--content <text>', 'Full replacement file content')
.option('--file <path>', 'Path to replacement file content')
.option('--mime-type <type>', 'MIME/content type')
.option('--format <fmt>', 'Output format', 'json')
.action(async (agentId, fileId, opts) => {
try {
const hasContent = opts.content !== undefined || opts.file !== undefined;
const payload = {
name: opts.name,
mimeType: opts.mimeType,
...(hasContent ? { content: readText(opts, 'file content') } : {}),
};
if (Object.values(payload).every((value) => value === undefined)) {
throw new Error('Provide updates via --name, --content, --file, or --mime-type');
}
const result = await new AgentledClient().updateAgentFile(agentId, fileId, payload);
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
files
.command('delete <agent-id> <file-id>')
.description('Permanently delete a file attached to an agent')
.option('--format <fmt>', 'Output format', 'json')
.action(async (agentId, fileId, opts) => {
try {
const result = await new AgentledClient().deleteAgentFile(agentId, fileId);
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
const workspaceSkills = agents
.command('workspace-skills')
.description('Manage workspace-created agent skills. Skill app/action fields are recommendations, not permission grants.');
workspaceSkills
.command('list')
.description('List workspace skills')
.option('--status <status>', 'Filter by status: draft, published, archived')
.option('--limit <n>', 'Maximum rows to return', parseInt)
.option('--format <fmt>', 'Output format', 'json')
.action(async (opts) => {
try {
const result = await new AgentledClient().listWorkspaceSkills({
status: opts.status,
limit: opts.limit,
});
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
workspaceSkills
.command('get <skill-id>')
.description('Get a workspace skill')
.option('--format <fmt>', 'Output format', 'json')
.action(async (skillId, opts) => {
try {
const result = await new AgentledClient().getWorkspaceSkill(skillId);
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
workspaceSkills
.command('import')
.description('Import a local or GitHub skill file into a draft workspace skill. Recommended apps/actions in the file do not grant access.')
.option('--file <path>', 'Path to a Markdown, SKILL.md, or JSON skill file')
.option('--url <url>', 'HTTPS URL to a Markdown, SKILL.md, or JSON skill file; GitHub blob URLs are converted to raw URLs')
.option('--format <fmt>', 'Output format', 'json')
.action(async (opts) => {
try {
const result = await new AgentledClient().importWorkspaceSkill(buildWorkspaceSkillImportPayload(opts));
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
workspaceSkills
.command('create')
.description('Create a draft workspace skill backed by an existing AgentFile. Recommended apps/actions do not grant access.')
.requiredOption('--name <name>', 'Skill name')
.requiredOption('--content-file-id <id>', 'AgentFile ID containing the skill body/instructions')
.option('--description <text>', 'Skill description')
.option('--category <category>', 'Skill category')
.option('--allowed-apps <ids>', 'Comma-separated recommended app IDs; does not grant app access')
.option('--allowed-actions <ids>', 'Comma-separated recommended action IDs; does not grant action access')
.option('--approval-policy <json>', 'Approval policy JSON object')
.option('--risk-profile <json>', 'Risk profile JSON object')
.option('--relevance-rules <json>', 'Relevance rules JSON object')
.option('--metadata <json>', 'Metadata JSON object')
.option('--input <json>', 'Full workspace skill JSON payload (overrides CLI flags)')
.option('--file <path>', 'Path to JSON payload file')
.option('--format <fmt>', 'Output format', 'json')
.action(async (opts) => {
try {
const payload = opts.input || opts.file
? readJson(opts, 'workspace skill payload')
: {
name: opts.name,
contentFileId: opts.contentFileId,
description: opts.description,
category: opts.category,
allowedApps: parseCsvList(opts.allowedApps),
allowedActions: parseCsvList(opts.allowedActions),
approvalPolicy: parseJsonOption(opts.approvalPolicy, 'approval policy'),
riskProfile: parseJsonOption(opts.riskProfile, 'risk profile'),
relevanceRules: parseJsonOption(opts.relevanceRules, 'relevance rules'),
metadata: parseJsonOption(opts.metadata, 'metadata'),
};
const result = await new AgentledClient().createWorkspaceSkill(payload);
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
workspaceSkills
.command('update <skill-id>')
.description('Update a draft workspace skill. Recommended apps/actions do not grant access.')
.option('--name <name>', 'Skill name')
.option('--content-file-id <id>', 'AgentFile ID containing the skill body/instructions')
.option('--description <text>', 'Skill description')
.option('--category <category>', 'Skill category')
.option('--allowed-apps <ids>', 'Comma-separated recommended app IDs; does not grant app access')
.option('--allowed-actions <ids>', 'Comma-separated recommended action IDs; does not grant action access')
.option('--approval-policy <json>', 'Approval policy JSON object')
.option('--risk-profile <json>', 'Risk profile JSON object')
.option('--relevance-rules <json>', 'Relevance rules JSON object')
.option('--metadata <json>', 'Metadata JSON object')
.option('--input <json>', 'Full workspace skill update payload (overrides CLI flags)')
.option('--file <path>', 'Path to JSON update payload file')
.option('--format <fmt>', 'Output format', 'json')
.action(async (skillId, opts) => {
try {
const payload = opts.input || opts.file
? readJson(opts, 'workspace skill update payload')
: {
name: opts.name,
contentFileId: opts.contentFileId,
description: opts.description,
category: opts.category,
allowedApps: parseCsvList(opts.allowedApps),
allowedActions: parseCsvList(opts.allowedActions),
approvalPolicy: parseJsonOption(opts.approvalPolicy, 'approval policy'),
riskProfile: parseJsonOption(opts.riskProfile, 'risk profile'),
relevanceRules: parseJsonOption(opts.relevanceRules, 'relevance rules'),
metadata: parseJsonOption(opts.metadata, 'metadata'),
};
if (Object.values(payload).every((value) => value === undefined)) {
throw new Error('Provide updates via flags, --input, or --file');
}
const result = await new AgentledClient().updateWorkspaceSkill(skillId, payload);
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
workspaceSkills
.command('publish <skill-id>')
.description('Publish a draft workspace skill')
.option('--format <fmt>', 'Output format', 'json')
.action(async (skillId, opts) => {
try {
const result = await new AgentledClient().publishWorkspaceSkill(skillId);
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
workspaceSkills
.command('archive <skill-id>')
.description('Archive a workspace skill')
.option('--format <fmt>', 'Output format', 'json')
.action(async (skillId, opts) => {
try {
const result = await new AgentledClient().archiveWorkspaceSkill(skillId);
printOutput(result, opts.format);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
printError(message);
}
});
}
//# sourceMappingURL=agents.js.map