BrowserCLI: complete plugin, integration, workflow, policy command surface
Some checks failed
Stuffle/agos-client/pipeline/head There was a failure building this commit

- plugin: add search, enable, disable, uninstall (was list/get only)
- integration: add catalog and providers subcommands; rename list→instances alias
- workflow: add create and status subcommands (was list/get/run/delete)
- policy: add create and delete subcommands (was list/get only)
- Update all MANUAL_ENTRIES to reflect expanded surfaces
- Error messages updated to reference correct subcommand sets

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
2026-08-23 11:49:43 +05:30
parent 65711c25ea
commit 20cd183fa0

View File

@@ -124,19 +124,44 @@ const MANUAL_ENTRIES: Record<string, ManualEntry> = {
examples: ['task list', 'task get task_123'],
},
plugin: {
summary: 'List and inspect plugins available to this workspace.',
usage: ['plugin list', 'plugin get <id>'],
examples: ['plugin list', 'plugin get plugin_123'],
summary: 'List, search, enable, disable, and remove plugins.',
usage: ['plugin list', 'plugin search <query>', 'plugin get <id>', 'plugin enable <id>', 'plugin disable <id>', 'plugin uninstall <id>'],
actions: [
{ action: 'plugin list', description: 'List all plugins with trust level and scan status.' },
{ action: 'plugin search <query>', description: 'Filter plugins by name or description.' },
{ action: 'plugin get <id>', description: 'Fetch full details for a single plugin.' },
{ action: 'plugin enable <id>', description: 'Enable a plugin for use by agents.' },
{ action: 'plugin disable <id>', description: 'Disable a plugin without deleting it.' },
{ action: 'plugin uninstall <id>', description: 'Permanently delete a plugin.' },
],
examples: ['plugin list', 'plugin search web', 'plugin enable plugin_123'],
},
integration: {
summary: 'Inspect integration instances and connectivity.',
usage: ['integration list', 'integration get <id>', 'integration test <id>', 'integration delete <id>'],
examples: ['integration list', 'integration test instance_123'],
summary: 'Inspect the integration catalog, providers, instances, and connectivity.',
usage: ['integration catalog [search]', 'integration providers [category]', 'integration instances', 'integration get <id>', 'integration create --provider-id=<id> --name=<name>', 'integration test <id>', 'integration delete <id>'],
actions: [
{ action: 'integration catalog [query]', description: 'Browse the integration catalog.' },
{ action: 'integration providers [category]', description: 'List configured integration providers.' },
{ action: 'integration instances', description: 'List active integration instances.' },
{ action: 'integration get <id>', description: 'Fetch full details for a single instance.' },
{ action: 'integration create --provider-id=<id> --name=<name>', description: 'Create a new integration instance.' },
{ action: 'integration test <id>', description: 'Test connectivity for an instance.' },
{ action: 'integration delete <id>', description: 'Delete an integration instance.' },
],
examples: ['integration catalog', 'integration providers', 'integration instances', 'integration test instance_123'],
},
workflow: {
summary: 'Inspect, create, run, and monitor workflows.',
usage: ['workflow list', 'workflow get <id>', 'workflow run <id> [input]', 'workflow delete <id>'],
examples: ['workflow list', 'workflow run wf_autoblogger_v1 Draft a launch note'],
usage: ['workflow list', 'workflow get <id>', 'workflow create <name>', 'workflow status <id>', 'workflow run <id> [input]', 'workflow delete <id>'],
actions: [
{ action: 'workflow list', description: 'List all workflows with execution counts.' },
{ action: 'workflow get <id>', description: 'Fetch full definition and metadata.' },
{ action: 'workflow create <name>', description: 'Create a new workflow scaffold.' },
{ action: 'workflow status <id>', description: 'Show status and recent executions for a workflow.' },
{ action: 'workflow run <id> [input]', description: 'Trigger a workflow execution.' },
{ action: 'workflow delete <id>', description: 'Delete a workflow.' },
],
examples: ['workflow list', 'workflow status wf_123', 'workflow run wf_autoblogger_v1 Draft a launch note'],
},
model: {
summary: 'Inspect model registrations, providers, and connectivity.',
@@ -168,13 +193,15 @@ const MANUAL_ENTRIES: Record<string, ManualEntry> = {
examples: ['doc search how to create an agent', 'doc search "PKCE login"', 'doc list'],
},
policy: {
summary: 'List and inspect policies governing agent behavior.',
usage: ['policy list', 'policy get <id>'],
summary: 'List, inspect, create, and delete policies governing agent behavior.',
usage: ['policy list', 'policy get <id>', 'policy create <name> [--type=allow|deny] [--priority=N]', 'policy delete <id>'],
actions: [
{ action: 'policy list', description: 'List all policies with type and status.' },
{ action: 'policy list [--type=...]', description: 'List all policies with type, priority, and enabled state.' },
{ action: 'policy get <id>', description: 'Fetch full details for a single policy.' },
{ action: 'policy create <name> [--type=allow|deny] [--priority=N]', description: 'Create a new policy.' },
{ action: 'policy delete <id>', description: 'Permanently delete a policy.' },
],
examples: ['policy list', 'policy get policy_123'],
examples: ['policy list', 'policy create "Block external calls" --type=deny --priority=10', 'policy delete policy_123'],
},
system: {
summary: 'Show system health, identity, and quota overview.',
@@ -358,14 +385,33 @@ async function execTaskCommand(args: string[]): Promise<CmdResult> {
}
async function execPluginCommand(args: string[]): Promise<CmdResult> {
const [subcmd, id] = args
const [subcmd, id, ...rest] = args
switch (subcmd) {
case 'list': {
const search = rest.find(a => a.startsWith('--search='))?.split('=')[1]
const res = await pluginsApi.list({ page_size: 50 })
if (!res.items.length) return { output: '\x1b[90mNo plugins found.\x1b[0m' }
const header = `\x1b[1m\x1b[36m${'ID'.padEnd(36)} ${'NAME'.padEnd(28)} STATUS\x1b[0m`
const rows = res.items.map((p: ApiPlugin) =>
`${p.id.padEnd(36)} ${p.name.slice(0, 28).padEnd(28)} ${p.scan_status ?? '—'}`
const items = search
? res.items.filter((p: ApiPlugin) => p.name.toLowerCase().includes(search.toLowerCase()))
: res.items
if (!items.length) return { output: '\x1b[90mNo plugins found.\x1b[0m' }
const header = `\x1b[1m\x1b[36m${'ID'.padEnd(36)} ${'NAME'.padEnd(28)} ${'TRUST'.padEnd(12)} STATUS\x1b[0m`
const rows = items.map((p: ApiPlugin) =>
`${p.id.padEnd(36)} ${p.name.slice(0, 28).padEnd(28)} ${String(p.trust_level ?? '—').padEnd(12)} ${p.scan_status ?? '—'}`
)
return { output: [header, ...rows].join('\r\n') }
}
case 'search': {
const query = [id, ...rest].filter(Boolean).join(' ').trim()
if (!query) return { output: '\x1b[31mUsage: plugin search <query>\x1b[0m', error: true }
const res = await pluginsApi.list({ page_size: 100 })
const matches = res.items.filter((p: ApiPlugin) =>
p.name.toLowerCase().includes(query.toLowerCase()) ||
(p.description ?? '').toLowerCase().includes(query.toLowerCase())
)
if (!matches.length) return { output: `\x1b[90mNo plugins matching: ${query}\x1b[0m` }
const header = `\x1b[1m\x1b[36m${'ID'.padEnd(36)} ${'NAME'.padEnd(28)} ${'TRUST'.padEnd(12)} STATUS\x1b[0m`
const rows = matches.map((p: ApiPlugin) =>
`${p.id.padEnd(36)} ${p.name.slice(0, 28).padEnd(28)} ${String(p.trust_level ?? '—').padEnd(12)} ${p.scan_status ?? '—'}`
)
return { output: [header, ...rows].join('\r\n') }
}
@@ -374,16 +420,34 @@ async function execPluginCommand(args: string[]): Promise<CmdResult> {
const p = await pluginsApi.get(id)
return { output: formatJson(p) }
}
case 'enable': {
if (!id) return { output: '\x1b[31mUsage: plugin enable <id>\x1b[0m', error: true }
const r = await pluginsApi.enable(id)
return { output: `\x1b[32m✓ Plugin ${id} enabled (enabled=${r.enabled})\x1b[0m` }
}
case 'disable': {
if (!id) return { output: '\x1b[31mUsage: plugin disable <id>\x1b[0m', error: true }
const r = await pluginsApi.disable(id)
return { output: `\x1b[33m○ Plugin ${id} disabled (enabled=${r.enabled})\x1b[0m` }
}
case 'uninstall':
case 'delete': {
if (!id) return { output: `\x1b[31mUsage: plugin ${subcmd} <id>\x1b[0m`, error: true }
await pluginsApi.delete(id)
return { output: `\x1b[31mPlugin ${id} deleted.\x1b[0m` }
}
default:
return { output: `\x1b[31mUnknown plugin subcommand: ${subcmd}\x1b[0m\r\nUsage: plugin [list|get]`, error: true }
return { output: `\x1b[31mUnknown plugin subcommand: ${subcmd}\x1b[0m\r\nUsage: plugin [list|search|get|enable|disable|uninstall]`, error: true }
}
}
async function execIntegrationCommand(args: string[]): Promise<CmdResult> {
const [subcmd, id] = args
const [subcmd, id, ...rest] = args
switch (subcmd) {
case 'instances':
case 'list': {
const res = await integrationsApi.instances()
const category = rest.find(a => a.startsWith('--category='))?.split('=')[1]
const res = await integrationsApi.instances(category ? { category } : undefined)
if (!res.length) return { output: '\x1b[90mNo integration instances found.\x1b[0m' }
const header = `\x1b[1m\x1b[36m${'ID'.padEnd(36)} ${'NAME'.padEnd(28)} ${'PROVIDER'.padEnd(20)} STATUS\x1b[0m`
const rows = res.map((i: ApiIntegrationInstance) =>
@@ -391,11 +455,39 @@ async function execIntegrationCommand(args: string[]): Promise<CmdResult> {
)
return { output: [header, ...rows].join('\r\n') }
}
case 'catalog': {
const search = [id, ...rest].filter(Boolean).join(' ').trim() || undefined
const entries = await integrationsApi.catalog(search ? { search } : undefined)
if (!entries.length) return { output: '\x1b[90mNo catalog entries found.\x1b[0m' }
const header = `\x1b[1m\x1b[36m${'SLUG'.padEnd(24)} ${'NAME'.padEnd(28)} ${'CATEGORY'.padEnd(16)} STATUS\x1b[0m`
const rows = entries.map((e) =>
`${String(e.slug ?? '—').padEnd(24)} ${String(e.name ?? '—').slice(0, 28).padEnd(28)} ${String(e.category ?? '—').slice(0, 16).padEnd(16)} ${e.status ?? '—'}`
)
return { output: [header, ...rows].join('\r\n') }
}
case 'providers': {
const category = id || undefined
const res = await integrationsApi.providers(category)
if (!res.length) return { output: '\x1b[90mNo integration providers found.\x1b[0m' }
const header = `\x1b[1m\x1b[36m${'ID'.padEnd(36)} ${'NAME'.padEnd(28)} ${'CATEGORY'.padEnd(16)} AUTH\x1b[0m`
const rows = res.map((p) =>
`${p.id.padEnd(36)} ${p.name.slice(0, 28).padEnd(28)} ${String(p.category ?? '—').slice(0, 16).padEnd(16)} ${p.auth_type ?? '—'}`
)
return { output: [header, ...rows].join('\r\n') }
}
case 'get': {
if (!id) return { output: '\x1b[31mUsage: integration get <id>\x1b[0m', error: true }
const i = await integrationsApi.getInstance(id)
return { output: formatJson(i) }
}
case 'create': {
const providerId = rest.find(a => a.startsWith('--provider-id='))?.split('=')[1] ?? id
const name = rest.find(a => a.startsWith('--name='))?.split('=')[1]
if (!providerId || !name) return { output: '\x1b[31mUsage: integration create --provider-id=<id> --name=<name>\x1b[0m', error: true }
const body = { provider_id: providerId, name, config: {} }
const created = await integrationsApi.createInstance(body)
return { output: `\x1b[32m✓ Created integration instance ${created.id} (${created.name})\x1b[0m` }
}
case 'test': {
if (!id) return { output: '\x1b[31mUsage: integration test <id>\x1b[0m', error: true }
const r = await integrationsApi.testInstance(id)
@@ -408,7 +500,7 @@ async function execIntegrationCommand(args: string[]): Promise<CmdResult> {
return { output: `\x1b[31mIntegration ${id} deleted.\x1b[0m` }
}
default:
return { output: `\x1b[31mUnknown integration subcommand: ${subcmd}\x1b[0m\r\nUsage: integration [list|get|test|delete]`, error: true }
return { output: `\x1b[31mUnknown integration subcommand: ${subcmd}\x1b[0m\r\nUsage: integration [catalog|providers|instances|get|create|test|delete]`, error: true }
}
}
@@ -418,9 +510,9 @@ async function execWorkflowCommand(args: string[]): Promise<CmdResult> {
case 'list': {
const res = await workflowsApi.list({ page_size: 50 })
if (!res.items.length) return { output: '\x1b[90mNo workflows found.\x1b[0m' }
const header = `\x1b[1m\x1b[36m${'ID'.padEnd(36)} ${'NAME'.padEnd(32)} STATUS\x1b[0m`
const header = `\x1b[1m\x1b[36m${'ID'.padEnd(36)} ${'NAME'.padEnd(32)} ${'EXECUTIONS'.padEnd(10)} STATUS\x1b[0m`
const rows = res.items.map((w: ApiWorkflow) =>
`${w.id.padEnd(36)} ${w.name.slice(0, 32).padEnd(32)} \x1b[${stateColor(w.status ?? '')}m${w.status ?? '—'}\x1b[0m`
`${w.id.padEnd(36)} ${w.name.slice(0, 32).padEnd(32)} ${String(w.execution_count ?? 0).padEnd(10)} \x1b[${stateColor(w.status ?? '')}m${w.status ?? '—'}\x1b[0m`
)
return { output: [header, ...rows].join('\r\n') }
}
@@ -429,10 +521,35 @@ async function execWorkflowCommand(args: string[]): Promise<CmdResult> {
const w = await workflowsApi.get(id)
return { output: formatJson(w) }
}
case 'create': {
const name = [id, ...rest].filter(a => !a.startsWith('--')).join(' ').trim()
const desc = rest.find(a => a.startsWith('--description='))?.split('=')[1]
if (!name) return { output: '\x1b[31mUsage: workflow create <name> [--description=...]\x1b[0m', error: true }
const w = await workflowsApi.create({ name, description: desc })
return { output: `\x1b[32m✓ Workflow created:\x1b[0m ${w.id} ${w.name}` }
}
case 'status': {
if (!id) return { output: '\x1b[31mUsage: workflow status <id>\x1b[0m', error: true }
const w = await workflowsApi.get(id)
const execs = await workflowsApi.executions(id, 5)
const lines = [
`\x1b[1mWorkflow:\x1b[0m ${w.name} (${w.id})`,
`\x1b[1mStatus:\x1b[0m \x1b[${stateColor(w.status ?? '')}m${w.status ?? '—'}\x1b[0m`,
`\x1b[1mRuns:\x1b[0m ${w.execution_count ?? 0}`,
`\x1b[1mLast run:\x1b[0m ${w.last_execution_at ?? '—'} (${w.last_execution_status ?? '—'})`,
'',
'\x1b[1mRecent executions:\x1b[0m',
...(execs.length
? execs.map(e => ` ${e.id} \x1b[${stateColor(e.status)}m${e.status.padEnd(12)}\x1b[0m ${e.started_at}`)
: [' \x1b[90m(none)\x1b[0m']),
]
return { output: lines.join('\r\n') }
}
case 'run': {
if (!id) return { output: '\x1b[31mUsage: workflow run <id> [input]\x1b[0m', error: true }
const input = rest.join(' ') || undefined
const ex = await workflowsApi.run(id, { agent_id: id, parameters: input ? { goal: input } : {} })
const input = rest.filter(a => !a.startsWith('--')).join(' ') || undefined
const agentId = rest.find(a => a.startsWith('--agent-id='))?.split('=')[1] ?? id
const ex = await workflowsApi.run(id, { agent_id: agentId, parameters: input ? { goal: input } : {} })
return { output: `\x1b[32mExecution started:\x1b[0m ${formatJson(ex)}` }
}
case 'delete': {
@@ -441,7 +558,7 @@ async function execWorkflowCommand(args: string[]): Promise<CmdResult> {
return { output: `\x1b[31mWorkflow ${id} deleted.\x1b[0m` }
}
default:
return { output: `\x1b[31mUnknown workflow subcommand: ${subcmd}\x1b[0m\r\nUsage: workflow [list|get|run|delete]`, error: true }
return { output: `\x1b[31mUnknown workflow subcommand: ${subcmd}\x1b[0m\r\nUsage: workflow [list|get|create|status|run|delete]`, error: true }
}
}
@@ -689,10 +806,11 @@ async function execDocCommand(args: string[]): Promise<CmdResult> {
// ── Policy commands ──────────────────────────────────────────────────────────
async function execPolicyCommand(args: string[]): Promise<CmdResult> {
const [subcmd, id] = args
const [subcmd, id, ...rest] = args
switch (subcmd) {
case 'list': {
const res = await policiesApi.list({ page_size: 50 })
const policyType = rest.find(a => a.startsWith('--type='))?.split('=')[1]
const res = await policiesApi.list({ page_size: 50, policy_type: policyType })
const items = res.items ?? []
if (!items.length) return { output: '\x1b[90mNo policies found.\x1b[0m' }
const header = `\x1b[1m\x1b[36m${'ID'.padEnd(36)} ${'NAME'.padEnd(24)} ${'TYPE'.padEnd(14)} ${'PRIORITY'.padEnd(8)} ENABLED\x1b[0m`
@@ -706,8 +824,21 @@ async function execPolicyCommand(args: string[]): Promise<CmdResult> {
const p = await policiesApi.get(id)
return { output: formatJson(p) }
}
case 'create': {
const name = [id, ...rest].filter(a => !a.startsWith('--')).join(' ').trim()
const policyType = rest.find(a => a.startsWith('--type='))?.split('=')[1] ?? 'allow'
const priority = Number(rest.find(a => a.startsWith('--priority='))?.split('=')[1] ?? 50)
if (!name) return { output: '\x1b[31mUsage: policy create <name> [--type=allow|deny] [--priority=N]\x1b[0m', error: true }
const p = await policiesApi.create({ name, policy_type: policyType, priority })
return { output: `\x1b[32m✓ Policy created:\x1b[0m ${p.id} ${p.name} (type=${p.policy_type}, priority=${p.priority})` }
}
case 'delete': {
if (!id) return { output: '\x1b[31mUsage: policy delete <id>\x1b[0m', error: true }
await policiesApi.delete(id)
return { output: `\x1b[31mPolicy ${id} deleted.\x1b[0m` }
}
default:
return { output: `\x1b[31mUnknown policy subcommand: ${subcmd}\x1b[0m\r\nUsage: policy [list|get]`, error: true }
return { output: `\x1b[31mUnknown policy subcommand: ${subcmd}\x1b[0m\r\nUsage: policy [list|get|create|delete]`, error: true }
}
}