runwork 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled-types/core-agent.d.ts +8 -0
- package/bundled-types/core-base-agent.d.ts +4 -0
- package/bundled-types/core-types.d.ts +101 -0
- package/bundled-types/core-workspace.d.ts +10 -0
- package/bundled-types/index.d.ts +2 -0
- package/bundled-types/shared/core-types.d.ts +101 -0
- package/bundled-types/shared/shared/core-types.d.ts +101 -0
- package/bundled-types/task-delegation/index.d.ts +50 -0
- package/bundled-types/task-delegation/sandbox-bridge.d.ts +88 -0
- package/bundled-types/task-delegation/session-manager.d.ts +86 -0
- package/bundled-types/task-delegation/stream-parser.d.ts +25 -0
- package/bundled-types/task-delegation/task-delegation/index.d.ts +30 -0
- package/bundled-types/task-delegation/task-delegation/sandbox-bridge.d.ts +88 -0
- package/bundled-types/task-delegation/task-delegation/session-manager.d.ts +86 -0
- package/bundled-types/task-delegation/task-delegation/stream-parser.d.ts +25 -0
- package/bundled-types/task-delegation/task-delegation/types.d.ts +165 -0
- package/bundled-types/task-delegation/types.d.ts +336 -0
- package/bundled-types/task-delegation.d.ts +2 -0
- package/dist/__tests__/curl-parser.test.d.ts +1 -0
- package/dist/__tests__/curl-parser.test.js +250 -0
- package/dist/__tests__/types-manager.test.js +22 -18
- package/dist/agents/claude-code.d.ts +10 -0
- package/dist/agents/claude-code.js +50 -0
- package/dist/agents/claude-desktop.d.ts +10 -0
- package/dist/agents/claude-desktop.js +141 -0
- package/dist/agents/codex.d.ts +10 -0
- package/dist/agents/codex.js +45 -0
- package/dist/agents/copilot-cli.d.ts +10 -0
- package/dist/agents/copilot-cli.js +46 -0
- package/dist/agents/copilot-vscode.d.ts +10 -0
- package/dist/agents/copilot-vscode.js +37 -0
- package/dist/agents/cursor.d.ts +10 -0
- package/dist/agents/cursor.js +55 -0
- package/dist/agents/detect.d.ts +4 -0
- package/dist/agents/detect.js +43 -0
- package/dist/agents/gemini.d.ts +10 -0
- package/dist/agents/gemini.js +47 -0
- package/dist/agents/types.d.ts +35 -0
- package/dist/agents/types.js +13 -0
- package/dist/agents/utils/json-config.d.ts +14 -0
- package/dist/agents/utils/json-config.js +42 -0
- package/dist/agents/utils/toml-config.d.ts +8 -0
- package/dist/agents/utils/toml-config.js +27 -0
- package/dist/agents/windsurf.d.ts +10 -0
- package/dist/agents/windsurf.js +50 -0
- package/dist/api/client.d.ts +97 -1
- package/dist/api/client.js +199 -0
- package/dist/auth/store.js +18 -0
- package/dist/commands/agents.d.ts +2 -0
- package/dist/commands/agents.js +160 -0
- package/dist/commands/api-keys.d.ts +2 -0
- package/dist/commands/api-keys.js +95 -0
- package/dist/commands/apps.d.ts +2 -0
- package/dist/commands/apps.js +82 -0
- package/dist/commands/components.d.ts +2 -0
- package/dist/commands/components.js +43 -0
- package/dist/commands/endpoints.d.ts +2 -0
- package/dist/commands/endpoints.js +188 -0
- package/dist/commands/entities.d.ts +2 -0
- package/dist/commands/entities.js +198 -0
- package/dist/commands/files.d.ts +2 -0
- package/dist/commands/files.js +192 -0
- package/dist/commands/info.d.ts +1 -0
- package/dist/commands/info.js +71 -127
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +10 -8
- package/dist/commands/integrations.js +105 -12
- package/dist/commands/mcp.d.ts +2 -0
- package/dist/commands/mcp.js +117 -0
- package/dist/commands/schedules.d.ts +2 -0
- package/dist/commands/schedules.js +221 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +199 -0
- package/dist/commands/skills.d.ts +2 -0
- package/dist/commands/skills.js +134 -0
- package/dist/commands/sync.d.ts +2 -0
- package/dist/commands/sync.js +146 -0
- package/dist/commands/workflows.d.ts +2 -0
- package/dist/commands/workflows.js +187 -0
- package/dist/generated/bundled-types.js +35 -33
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/index.js +26 -0
- package/dist/logs/tailer.js +41 -1
- package/dist/types.d.ts +204 -0
- package/dist/ui/banner.js +7 -6
- package/dist/utils/app-info.d.ts +66 -0
- package/dist/utils/app-info.js +150 -0
- package/dist/utils/curl-parser.d.ts +12 -0
- package/dist/utils/curl-parser.js +44 -0
- package/dist/utils/data-input.d.ts +1 -0
- package/dist/utils/data-input.js +38 -0
- package/dist/workspace/resolve.d.ts +19 -0
- package/dist/workspace/resolve.js +100 -0
- package/package.json +3 -2
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { requireAuth } from '../auth/store.js';
|
|
6
|
+
import { ApiClient } from '../api/client.js';
|
|
7
|
+
import { getAdapterBySlug } from '../agents/detect.js';
|
|
8
|
+
import { RUNWORK_MCP_PREFIX } from '../agents/types.js';
|
|
9
|
+
function loadSetupState(filePath) {
|
|
10
|
+
if (!existsSync(filePath))
|
|
11
|
+
return null;
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function syncFromState(state, statePath, credentials, dryRun) {
|
|
20
|
+
const client = new ApiClient(credentials);
|
|
21
|
+
console.log(`Syncing workspace: ${state.workspaceName || state.workspaceId}`);
|
|
22
|
+
// Fetch latest data
|
|
23
|
+
const [allSkills, mcpServers, externalSkills] = await Promise.all([
|
|
24
|
+
client.listWorkspaceSkills(state.workspaceId),
|
|
25
|
+
client.listMcpServers(state.workspaceId),
|
|
26
|
+
client.listExternalSkills(state.workspaceId),
|
|
27
|
+
]);
|
|
28
|
+
// Build entries
|
|
29
|
+
const wsPrefix = state.workspaceId.substring(0, 8);
|
|
30
|
+
const baseUrl = credentials.baseUrl || 'https://runwork.ai';
|
|
31
|
+
const mcpEntries = mcpServers
|
|
32
|
+
.filter(s => s.enabled)
|
|
33
|
+
.map(s => ({
|
|
34
|
+
name: `${RUNWORK_MCP_PREFIX}${wsPrefix}-${s.name}`,
|
|
35
|
+
url: s.url,
|
|
36
|
+
transport: s.transport,
|
|
37
|
+
headers: { Authorization: `Bearer ${credentials.apiKey}` },
|
|
38
|
+
}));
|
|
39
|
+
mcpEntries.push({
|
|
40
|
+
name: `${RUNWORK_MCP_PREFIX}${wsPrefix}-workspace`,
|
|
41
|
+
url: `${baseUrl}/api/workspaces/${state.workspaceId}/mcp`,
|
|
42
|
+
transport: 'streamable-http',
|
|
43
|
+
headers: { Authorization: `Bearer ${credentials.apiKey}` },
|
|
44
|
+
});
|
|
45
|
+
const baseSkillFiles = externalSkills.map(s => ({
|
|
46
|
+
name: s.name,
|
|
47
|
+
filename: s.name.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
|
|
48
|
+
content: s.content,
|
|
49
|
+
description: s.description,
|
|
50
|
+
}));
|
|
51
|
+
// Fetch all app skills in parallel
|
|
52
|
+
const allAppSkillFiles = [];
|
|
53
|
+
const appSkillEntries = allSkills.filter(s => s.type === 'app' && s.appId);
|
|
54
|
+
const appSkillResults = await Promise.all(appSkillEntries.map(async (s) => {
|
|
55
|
+
try {
|
|
56
|
+
const content = await client.getAppSkillContent(state.workspaceId, s.appId);
|
|
57
|
+
if (content && content.trim()) {
|
|
58
|
+
return {
|
|
59
|
+
name: s.name,
|
|
60
|
+
filename: s.name.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
|
|
61
|
+
content,
|
|
62
|
+
description: s.description || `App skill for ${s.name}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch { /* skip */ }
|
|
67
|
+
return null;
|
|
68
|
+
}));
|
|
69
|
+
for (const r of appSkillResults) {
|
|
70
|
+
if (r)
|
|
71
|
+
allAppSkillFiles.push(r);
|
|
72
|
+
}
|
|
73
|
+
// Project scope: only current app's skill; User scope: all app skills
|
|
74
|
+
let projectAppSkillFile = null;
|
|
75
|
+
if (existsSync('.runwork.json')) {
|
|
76
|
+
try {
|
|
77
|
+
const config = JSON.parse(readFileSync('.runwork.json', 'utf-8'));
|
|
78
|
+
if (config.appName) {
|
|
79
|
+
const fname = config.appName.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
80
|
+
projectAppSkillFile = allAppSkillFiles.find(s => s.filename === fname) || null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch { /* ignore */ }
|
|
84
|
+
}
|
|
85
|
+
const userSkillFiles = [...baseSkillFiles, ...allAppSkillFiles];
|
|
86
|
+
const projectSkillFiles = projectAppSkillFile
|
|
87
|
+
? [...baseSkillFiles, projectAppSkillFile]
|
|
88
|
+
: [...baseSkillFiles];
|
|
89
|
+
if (dryRun) {
|
|
90
|
+
console.log(` Would update: ${mcpEntries.length} MCP servers, ${userSkillFiles.length} skills (user), ${projectSkillFiles.length} skills (project)`);
|
|
91
|
+
console.log(` Agents: ${state.configuredAgents.join(', ')}`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// Write to configured agents
|
|
95
|
+
const scopes = state.scope === 'both' ? ['project', 'user'] : [state.scope];
|
|
96
|
+
for (const slug of state.configuredAgents) {
|
|
97
|
+
const adapter = getAdapterBySlug(slug);
|
|
98
|
+
if (!adapter)
|
|
99
|
+
continue;
|
|
100
|
+
for (const scope of scopes) {
|
|
101
|
+
const skillFiles = scope === 'project' ? projectSkillFiles : userSkillFiles;
|
|
102
|
+
try {
|
|
103
|
+
if (adapter.supportsMcpScope(scope) && mcpEntries.length > 0) {
|
|
104
|
+
await adapter.writeMcpServers(mcpEntries, scope);
|
|
105
|
+
}
|
|
106
|
+
if (adapter.supportsSkills() && skillFiles.length > 0) {
|
|
107
|
+
await adapter.writeSkills(skillFiles, scope);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
console.warn(` [${adapter.name}] Failed (${scope}): ${err instanceof Error ? err.message : err}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
console.log(` [${adapter.name}] Updated`);
|
|
115
|
+
}
|
|
116
|
+
// Update state
|
|
117
|
+
state.lastSyncAt = new Date().toISOString();
|
|
118
|
+
state.mcpServers = mcpEntries.map(e => e.name);
|
|
119
|
+
state.skills = userSkillFiles.map(s => s.name);
|
|
120
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
121
|
+
}
|
|
122
|
+
export const syncCommand = new Command('sync')
|
|
123
|
+
.description('Refresh local agent configs with latest workspace skills and MCP servers')
|
|
124
|
+
.option('--dry-run', 'Show what would change without writing files')
|
|
125
|
+
.action(async (opts) => {
|
|
126
|
+
const credentials = requireAuth();
|
|
127
|
+
const dryRun = !!opts.dryRun;
|
|
128
|
+
const projectStatePath = join(process.cwd(), '.runwork', 'setup.json');
|
|
129
|
+
const userStatePath = join(homedir(), '.runwork', 'setup.json');
|
|
130
|
+
const projectState = loadSetupState(projectStatePath);
|
|
131
|
+
const userState = loadSetupState(userStatePath);
|
|
132
|
+
if (!projectState && !userState) {
|
|
133
|
+
console.error('No setup state found. Run `runwork setup` first.');
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
// Sync both states independently — even if same workspace, they may target different scopes/agents
|
|
137
|
+
if (projectState) {
|
|
138
|
+
await syncFromState(projectState, projectStatePath, credentials, dryRun);
|
|
139
|
+
}
|
|
140
|
+
if (userState) {
|
|
141
|
+
await syncFromState(userState, userStatePath, credentials, dryRun);
|
|
142
|
+
}
|
|
143
|
+
if (!dryRun) {
|
|
144
|
+
console.log('\nSync complete.');
|
|
145
|
+
}
|
|
146
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { requireAuth } from '../auth/store.js';
|
|
3
|
+
import { ApiClient } from '../api/client.js';
|
|
4
|
+
import { resolveWorkspace, resolveApp } from '../workspace/resolve.js';
|
|
5
|
+
import { shouldOutputJson, jsonOut } from '../utils/output.js';
|
|
6
|
+
function truncate(text, max) {
|
|
7
|
+
if (!text)
|
|
8
|
+
return '';
|
|
9
|
+
const first = text.split('\n')[0];
|
|
10
|
+
if (first.length <= max)
|
|
11
|
+
return first;
|
|
12
|
+
return first.slice(0, max - 3) + '...';
|
|
13
|
+
}
|
|
14
|
+
const listCommand = new Command('list')
|
|
15
|
+
.description('List workflows in a workspace')
|
|
16
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
17
|
+
.option('--app <name>', 'Filter by app name or ID')
|
|
18
|
+
.action(async (opts, command) => {
|
|
19
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
20
|
+
const credentials = requireAuth();
|
|
21
|
+
const client = new ApiClient(credentials);
|
|
22
|
+
const { workspaceId, workspaceName } = await resolveWorkspace(client, opts);
|
|
23
|
+
try {
|
|
24
|
+
let workflows = await client.listWorkflows(workspaceId);
|
|
25
|
+
if (opts.app) {
|
|
26
|
+
workflows = workflows.filter(w => w.appId === opts.app || w.appName === opts.app);
|
|
27
|
+
}
|
|
28
|
+
if (useJson) {
|
|
29
|
+
jsonOut({ workflows });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (workflows.length === 0) {
|
|
33
|
+
console.log('No workflows found.');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
console.log(`\nWorkspace: ${workspaceName || workspaceId}\n`);
|
|
37
|
+
console.log(' ' +
|
|
38
|
+
'Name'.padEnd(24) +
|
|
39
|
+
'App'.padEnd(20) +
|
|
40
|
+
'Mode'.padEnd(12) +
|
|
41
|
+
'Enabled'.padEnd(10) +
|
|
42
|
+
'Description');
|
|
43
|
+
console.log(' ' + '-'.repeat(80));
|
|
44
|
+
for (const w of workflows) {
|
|
45
|
+
const mode = w.deploymentMode ?? '';
|
|
46
|
+
const enabled = w.enabled === false ? 'no' : 'yes';
|
|
47
|
+
console.log(' ' +
|
|
48
|
+
w.name.padEnd(24) +
|
|
49
|
+
(w.appName || w.appId).slice(0, 18).padEnd(20) +
|
|
50
|
+
mode.padEnd(12) +
|
|
51
|
+
enabled.padEnd(10) +
|
|
52
|
+
truncate(w.description, 40));
|
|
53
|
+
}
|
|
54
|
+
console.log('');
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
console.error('Failed to list workflows:', err instanceof Error ? err.message : err);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
const triggerCommand = new Command('trigger')
|
|
62
|
+
.description('Trigger a workflow by name')
|
|
63
|
+
.argument('<name>', 'Workflow name')
|
|
64
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
65
|
+
.option('--app <name>', 'App name or ID (required if not in a project directory)')
|
|
66
|
+
.option('--params <json>', 'JSON string of parameters to pass to the workflow')
|
|
67
|
+
.option('--instance-id <id>', 'Custom instance ID for the workflow run')
|
|
68
|
+
.action(async (name, opts, command) => {
|
|
69
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
70
|
+
const credentials = requireAuth();
|
|
71
|
+
const client = new ApiClient(credentials);
|
|
72
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
73
|
+
const { appId, appName } = await resolveApp(client, workspaceId, opts);
|
|
74
|
+
let params;
|
|
75
|
+
if (opts.params) {
|
|
76
|
+
try {
|
|
77
|
+
params = JSON.parse(opts.params);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
console.error('Failed to parse --params: must be valid JSON');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const result = await client.triggerWorkflow(workspaceId, {
|
|
86
|
+
workflowName: name,
|
|
87
|
+
appId,
|
|
88
|
+
params,
|
|
89
|
+
instanceId: opts.instanceId,
|
|
90
|
+
});
|
|
91
|
+
if (useJson) {
|
|
92
|
+
jsonOut({ result });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
console.log(`Triggered workflow "${name}" on app "${appName || appId}".`);
|
|
96
|
+
console.log(` Instance ID: ${result.instanceId}`);
|
|
97
|
+
console.log(` Status: ${result.status}`);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
console.error('Failed to trigger workflow:', err instanceof Error ? err.message : err);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
const instancesCommand = new Command('instances')
|
|
105
|
+
.description('List workflow instances for an app')
|
|
106
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
107
|
+
.option('--app <name>', 'App name or ID (required if not in a project directory)')
|
|
108
|
+
.option('--workflow <name>', 'Filter by workflow name')
|
|
109
|
+
.option('--status <status>', 'Filter by instance status')
|
|
110
|
+
.option('--limit <n>', 'Maximum number of instances to return')
|
|
111
|
+
.action(async (opts, command) => {
|
|
112
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
113
|
+
const credentials = requireAuth();
|
|
114
|
+
const client = new ApiClient(credentials);
|
|
115
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
116
|
+
const { appId } = await resolveApp(client, workspaceId, opts);
|
|
117
|
+
try {
|
|
118
|
+
const data = await client.listWorkflowInstances(workspaceId, appId, {
|
|
119
|
+
workflowName: opts.workflow,
|
|
120
|
+
status: opts.status,
|
|
121
|
+
limit: opts.limit !== undefined ? Number(opts.limit) : undefined,
|
|
122
|
+
});
|
|
123
|
+
if (useJson) {
|
|
124
|
+
jsonOut({ instances: data.instances });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (data.instances.length === 0) {
|
|
128
|
+
console.log('No workflow instances found.');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
console.log('\n ' +
|
|
132
|
+
'ID'.padEnd(28) +
|
|
133
|
+
'Workflow'.padEnd(24) +
|
|
134
|
+
'Status'.padEnd(14) +
|
|
135
|
+
'Created At');
|
|
136
|
+
console.log(' ' + '-'.repeat(80));
|
|
137
|
+
for (const inst of data.instances) {
|
|
138
|
+
const created = inst.createdAt
|
|
139
|
+
? new Date(inst.createdAt).toLocaleString()
|
|
140
|
+
: '';
|
|
141
|
+
console.log(' ' +
|
|
142
|
+
inst.id.slice(0, 26).padEnd(28) +
|
|
143
|
+
inst.workflowName.padEnd(24) +
|
|
144
|
+
inst.status.padEnd(14) +
|
|
145
|
+
created);
|
|
146
|
+
}
|
|
147
|
+
console.log('');
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
console.error('Failed to list workflow instances:', err instanceof Error ? err.message : err);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
const instanceCommand = new Command('instance')
|
|
155
|
+
.description('Get details for a specific workflow instance')
|
|
156
|
+
.argument('<id>', 'Workflow instance ID')
|
|
157
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
158
|
+
.option('--app <name>', 'App name or ID (optional)')
|
|
159
|
+
.action(async (id, opts, command) => {
|
|
160
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
161
|
+
const credentials = requireAuth();
|
|
162
|
+
const client = new ApiClient(credentials);
|
|
163
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
164
|
+
let appId;
|
|
165
|
+
if (opts.app) {
|
|
166
|
+
const resolved = await resolveApp(client, workspaceId, opts);
|
|
167
|
+
appId = resolved.appId;
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const data = await client.getWorkflowInstance(workspaceId, id, appId);
|
|
171
|
+
if (useJson) {
|
|
172
|
+
jsonOut({ instance: data.instance });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
console.log(JSON.stringify(data.instance, null, 2));
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
console.error('Failed to get workflow instance:', err instanceof Error ? err.message : err);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
export const workflowsCommand = new Command('workflows')
|
|
183
|
+
.description('Manage workspace workflows')
|
|
184
|
+
.addCommand(listCommand)
|
|
185
|
+
.addCommand(triggerCommand)
|
|
186
|
+
.addCommand(instancesCommand)
|
|
187
|
+
.addCommand(instanceCommand);
|