UNPKG

@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
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