mirror of
https://github.com/openclaw/lobster.git
synced 2026-08-14 00:48:09 +00:00
feat: enrich commands.list with args schema + metadata
This commit is contained in:
@@ -1,4 +1,12 @@
|
||||
export const commandsListCommand = {
|
||||
import type { CommandMeta, LobsterCommand } from './types.js';
|
||||
|
||||
function parseDescriptionFromHelp(helpText: string): string {
|
||||
const firstLine = helpText.split('\n').find((l) => l.trim().length > 0) ?? '';
|
||||
// Expected pattern: "name — description" but fall back to the line as-is.
|
||||
return firstLine.includes('—') ? firstLine.split('—').slice(1).join('—').trim() : firstLine.trim();
|
||||
}
|
||||
|
||||
export const commandsListCommand: LobsterCommand = {
|
||||
name: 'commands.list',
|
||||
help() {
|
||||
return (
|
||||
@@ -7,9 +15,14 @@ export const commandsListCommand = {
|
||||
` commands.list\n\n` +
|
||||
`Notes:\n` +
|
||||
` - Intended for agents (e.g. Clawdbot) to discover available pipeline stages dynamically.\n` +
|
||||
` - Output includes the command name and a short description extracted from help().\n`
|
||||
` - Output includes name/description plus optional metadata (argsSchema/examples/sideEffects) when provided by commands.\n`
|
||||
);
|
||||
},
|
||||
meta: {
|
||||
description: 'List available Lobster pipeline commands',
|
||||
argsSchema: { type: 'object', properties: {}, required: [] },
|
||||
sideEffects: [],
|
||||
} satisfies CommandMeta,
|
||||
async run({ input, ctx }) {
|
||||
// Drain input
|
||||
for await (const _ of input) {
|
||||
@@ -18,16 +31,16 @@ export const commandsListCommand = {
|
||||
|
||||
const names = ctx.registry.list();
|
||||
const output = names.map((name) => {
|
||||
const cmd = ctx.registry.get(name);
|
||||
const cmd = ctx.registry.get(name) as LobsterCommand | undefined;
|
||||
const help = typeof cmd?.help === 'function' ? String(cmd.help()) : '';
|
||||
const firstLine = help.split('\n').find((l) => l.trim().length > 0) ?? '';
|
||||
|
||||
// Expected pattern: "name — description" but fall back to the line as-is.
|
||||
const desc = firstLine.includes('—') ? firstLine.split('—').slice(1).join('—').trim() : firstLine.trim();
|
||||
const description = cmd?.meta?.description ?? parseDescriptionFromHelp(help);
|
||||
|
||||
return {
|
||||
name,
|
||||
description: desc,
|
||||
description,
|
||||
argsSchema: cmd?.meta?.argsSchema ?? null,
|
||||
examples: cmd?.meta?.examples ?? null,
|
||||
sideEffects: cmd?.meta?.sideEffects ?? null,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,19 @@ function isInteractive(stdin) {
|
||||
|
||||
export const approveCommand = {
|
||||
name: 'approve',
|
||||
meta: {
|
||||
description: 'Require confirmation to continue',
|
||||
argsSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
prompt: { type: 'string', description: 'Approval prompt text', default: 'Approve?' },
|
||||
emit: { type: 'boolean', description: 'Force emit approval request + halt' },
|
||||
_: { type: 'array', items: { type: 'string' } },
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
sideEffects: [],
|
||||
},
|
||||
help() {
|
||||
return `approve — require confirmation to continue\n\nUsage:\n ... | approve --prompt "Send these emails?"\n ... | approve --emit --prompt "Send these emails?"\n\nModes:\n - Interactive (default): prompts on TTY and passes items through if approved.\n - Emit (--emit): returns an approval request object and stops the pipeline.\n\nNotes:\n - In tool mode (or non-interactive), this emits an approval request and halts.\n`;
|
||||
},
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
export const clawdInvokeCommand = {
|
||||
name: 'clawd.invoke',
|
||||
meta: {
|
||||
description: 'Call a local Clawdbot tool endpoint',
|
||||
argsSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: 'string', description: 'Clawdbot control URL (or CLAWD_URL)' },
|
||||
token: { type: 'string', description: 'Bearer token (or CLAWD_TOKEN)' },
|
||||
tool: { type: 'string', description: 'Tool name (e.g. message, cron, github, etc.)' },
|
||||
action: { type: 'string', description: 'Tool action' },
|
||||
'args-json': { type: 'string', description: 'JSON string of tool args' },
|
||||
sessionKey: { type: 'string', description: 'Optional session key attribution' },
|
||||
'session-key': { type: 'string', description: 'Alias for sessionKey' },
|
||||
dryRun: { type: 'boolean', description: 'Dry run' },
|
||||
'dry-run': { type: 'boolean', description: 'Alias for dryRun' },
|
||||
_: { type: 'array', items: { type: 'string' } },
|
||||
},
|
||||
required: ['tool', 'action'],
|
||||
},
|
||||
sideEffects: ['calls_clawd_tool'],
|
||||
},
|
||||
help() {
|
||||
return `clawd.invoke — call a local Clawdbot tool endpoint\n\n` +
|
||||
`Usage:\n` +
|
||||
|
||||
@@ -2,6 +2,18 @@ import { diffAndStore } from '../../state/store.js';
|
||||
|
||||
export const diffLastCommand = {
|
||||
name: 'diff.last',
|
||||
meta: {
|
||||
description: 'Compare current items to last stored snapshot',
|
||||
argsSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: { type: 'string', description: 'State key to diff against' },
|
||||
_: { type: 'array', items: { type: 'string' } },
|
||||
},
|
||||
required: ['key'],
|
||||
},
|
||||
sideEffects: ['writes_state'],
|
||||
},
|
||||
help() {
|
||||
return `diff.last — compare current items to last stored snapshot\n\nUsage:\n <items> | diff.last --key <stateKey>\n\nOutput:\n { changed, key, before, after }\n`;
|
||||
},
|
||||
|
||||
@@ -42,6 +42,18 @@ function isLikelyNoReply(from: string) {
|
||||
|
||||
export const emailTriageCommand = {
|
||||
name: "email.triage",
|
||||
meta: {
|
||||
description: "Deterministic bucketing + summary for email messages",
|
||||
argsSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
limit: { type: "number", description: "Maximum items to consume from input stream", default: 20 },
|
||||
_: { type: "array", items: { type: "string" } },
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
sideEffects: [],
|
||||
},
|
||||
help() {
|
||||
return (
|
||||
`email.triage — deterministic bucketing + summary for email messages\n\n` +
|
||||
|
||||
@@ -2,6 +2,19 @@ import { spawn } from 'node:child_process';
|
||||
|
||||
export const execCommand = {
|
||||
name: 'exec',
|
||||
meta: {
|
||||
description: 'Run an OS command',
|
||||
argsSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
json: { type: 'boolean', description: 'Parse stdout as JSON (single value).' },
|
||||
shell: { type: 'string', description: 'Run via /bin/sh -lc with this command line.' },
|
||||
_: { type: 'array', items: { type: 'string' }, description: 'Command + args.' },
|
||||
},
|
||||
required: ['_'],
|
||||
},
|
||||
sideEffects: ['local_exec'],
|
||||
},
|
||||
help() {
|
||||
return `exec — run an OS command\n\n` +
|
||||
`Usage:\n` +
|
||||
|
||||
@@ -29,6 +29,20 @@ function run(cmd: string, argv: string[], env: Record<string, string | undefined
|
||||
|
||||
export const gogGmailSearchCommand = {
|
||||
name: "gog.gmail.search",
|
||||
meta: {
|
||||
description: "Fetch Gmail threads via gog (JSON)",
|
||||
argsSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: { type: "string", description: "Gmail search query", default: "newer_than:1d" },
|
||||
max: { type: "number", description: "Max results", default: 20 },
|
||||
limit: { type: "number", description: "Alias for max" },
|
||||
_: { type: "array", items: { type: "string" } },
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
sideEffects: ['reads_email'],
|
||||
},
|
||||
help() {
|
||||
return (
|
||||
`gog.gmail.search — fetch Gmail messages via gog (JSON)\n\n` +
|
||||
|
||||
@@ -46,6 +46,19 @@ function parseDraft(item: any): Draft {
|
||||
|
||||
export const gogGmailSendCommand = {
|
||||
name: "gog.gmail.send",
|
||||
meta: {
|
||||
description: "Send Gmail messages via gog",
|
||||
argsSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
dryRun: { type: "boolean", description: "If true, do not send; echo drafts" },
|
||||
"dry-run": { type: "boolean", description: "Alias for dryRun" },
|
||||
_: { type: "array", items: { type: "string" } },
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
sideEffects: ['sends_email'],
|
||||
},
|
||||
help() {
|
||||
return (
|
||||
`gog.gmail.send — send Gmail messages via gog\n\n` +
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
export const headCommand = {
|
||||
name: 'head',
|
||||
meta: {
|
||||
description: 'Take first N items',
|
||||
argsSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
n: { type: 'number', description: 'Number of items to take', default: 10 },
|
||||
_: { type: 'array', items: { type: 'string' } },
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
sideEffects: [],
|
||||
},
|
||||
help() {
|
||||
return `head — take first N items\n\nUsage:\n head --n 10\n`;
|
||||
},
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
export const jsonCommand = {
|
||||
name: 'json',
|
||||
meta: {
|
||||
description: 'Render pipeline output as JSON',
|
||||
argsSchema: { type: 'object', properties: {}, required: [] },
|
||||
sideEffects: [],
|
||||
},
|
||||
help() {
|
||||
return `json — render pipeline output as JSON\n\nUsage:\n ... | json\n`;
|
||||
},
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
export const pickCommand = {
|
||||
name: 'pick',
|
||||
meta: {
|
||||
description: 'Project fields from objects',
|
||||
argsSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
_: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'First positional arg is a comma-separated list of fields',
|
||||
},
|
||||
},
|
||||
required: ['_'],
|
||||
},
|
||||
sideEffects: [],
|
||||
},
|
||||
help() {
|
||||
return `pick — project fields from objects\n\nUsage:\n ... | pick id,subject,from\n`;
|
||||
},
|
||||
|
||||
@@ -4,6 +4,17 @@ import { defaultStateDir, keyToPath } from '../../state/store.js';
|
||||
|
||||
export const stateGetCommand = {
|
||||
name: 'state.get',
|
||||
meta: {
|
||||
description: 'Read a JSON value from Lobster state',
|
||||
argsSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
_: { type: 'array', items: { type: 'string' }, description: 'Key' },
|
||||
},
|
||||
required: ['_'],
|
||||
},
|
||||
sideEffects: ['reads_state'],
|
||||
},
|
||||
help() {
|
||||
return `state.get — read a JSON value from Lobster state\n\nUsage:\n state.get <key>\n\nEnv:\n LOBSTER_STATE_DIR overrides storage directory\n`;
|
||||
},
|
||||
@@ -32,6 +43,17 @@ export const stateGetCommand = {
|
||||
|
||||
export const stateSetCommand = {
|
||||
name: 'state.set',
|
||||
meta: {
|
||||
description: 'Write a JSON value to Lobster state',
|
||||
argsSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
_: { type: 'array', items: { type: 'string' }, description: 'Key' },
|
||||
},
|
||||
required: ['_'],
|
||||
},
|
||||
sideEffects: ['writes_state'],
|
||||
},
|
||||
help() {
|
||||
return `state.set — write a JSON value to Lobster state\n\nUsage:\n <value> | state.set <key>\n\nNotes:\n - Consumes the entire input stream; stores a single JSON value.\n`;
|
||||
},
|
||||
|
||||
@@ -7,6 +7,11 @@ function stringifyCell(v) {
|
||||
|
||||
export const tableCommand = {
|
||||
name: 'table',
|
||||
meta: {
|
||||
description: 'Render items as a simple table',
|
||||
argsSchema: { type: 'object', properties: {}, required: [] },
|
||||
sideEffects: [],
|
||||
},
|
||||
help() {
|
||||
return `table — render items as a simple table\n\nUsage:\n ... | table\n\nNotes:\n - If items are objects, columns are union of keys (first 20 items).\n`;
|
||||
},
|
||||
|
||||
@@ -36,6 +36,21 @@ function compare(left, op, right) {
|
||||
|
||||
export const whereCommand = {
|
||||
name: 'where',
|
||||
meta: {
|
||||
description: 'Filter objects by a simple predicate',
|
||||
argsSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
_: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'First positional arg is an expression like field=value or minutes>=30',
|
||||
},
|
||||
},
|
||||
required: ['_'],
|
||||
},
|
||||
sideEffects: [],
|
||||
},
|
||||
help() {
|
||||
return `where — filter objects by a simple predicate\n\nUsage:\n ... | where unread=true\n ... | where minutes>=30\n ... | where sender.domain==example.com\n`;
|
||||
},
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
export type CommandMeta = {
|
||||
description?: string;
|
||||
argsSchema?: unknown;
|
||||
examples?: Array<{ args: Record<string, unknown>; description?: string }>;
|
||||
sideEffects?: string[];
|
||||
};
|
||||
|
||||
export type LobsterCommand = {
|
||||
name: string;
|
||||
help: () => string;
|
||||
run: (params: any) => Promise<any>;
|
||||
meta?: CommandMeta;
|
||||
};
|
||||
@@ -2,6 +2,11 @@ import { listWorkflows } from '../../workflows/registry.js';
|
||||
|
||||
export const workflowsListCommand = {
|
||||
name: 'workflows.list',
|
||||
meta: {
|
||||
description: 'List available Lobster workflows',
|
||||
argsSchema: { type: 'object', properties: {}, required: [] },
|
||||
sideEffects: [],
|
||||
},
|
||||
help() {
|
||||
return `workflows.list — list available Lobster workflows\n\nUsage:\n workflows.list\n\nNotes:\n - Intended for Clawdbot to discover workflows dynamically.\n`;
|
||||
},
|
||||
|
||||
@@ -12,6 +12,19 @@ const recipeRunners = {};
|
||||
|
||||
export const workflowsRunCommand = {
|
||||
name: 'workflows.run',
|
||||
meta: {
|
||||
description: 'Run a named Lobster workflow',
|
||||
argsSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Workflow name' },
|
||||
'args-json': { type: 'string', description: 'JSON string of workflow args' },
|
||||
_: { type: 'array', items: { type: 'string' } },
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
sideEffects: [],
|
||||
},
|
||||
help() {
|
||||
return `workflows.run — run a named Lobster workflow\n\nUsage:\n workflows.run --name <workflow> [--args-json '{...}']\n\nExample:\n workflows.run --name github.pr.monitor.notify --args-json '{"repo":"clawdbot/clawdbot","pr":1152}'\n`;
|
||||
},
|
||||
|
||||
@@ -42,4 +42,6 @@ test('commands.list returns command inventory including stdlib + workflows', asy
|
||||
assert.ok(self);
|
||||
assert.equal(typeof self.description, 'string');
|
||||
assert.ok(self.description.length > 0);
|
||||
// Schema should be present for commands that declare it.
|
||||
assert.ok(self.argsSchema);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user