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
package/dist/commands/info.js
CHANGED
|
@@ -2,16 +2,21 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { readFileSync, existsSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { shouldOutputJson, jsonOut } from '../utils/output.js';
|
|
5
|
+
import { fetchAppInfo } from '../utils/app-info.js';
|
|
5
6
|
import { bold, cyan, dim, green, yellow, gray } from '../ui/colors.js';
|
|
6
7
|
import { VERSION } from '../generated/version.js';
|
|
7
8
|
import { requireAuth } from '../auth/store.js';
|
|
8
9
|
import { ApiClient } from '../api/client.js';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
import { resolveWorkspace } from '../workspace/resolve.js';
|
|
11
|
+
function tryReadConfig() {
|
|
12
|
+
if (!existsSync('.runwork.json'))
|
|
13
|
+
return null;
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(readFileSync('.runwork.json', 'utf-8'));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
13
19
|
}
|
|
14
|
-
return JSON.parse(readFileSync('.runwork.json', 'utf-8'));
|
|
15
20
|
}
|
|
16
21
|
function readBlueprint(cwd) {
|
|
17
22
|
const blueprintPath = join(cwd, 'blueprint.json');
|
|
@@ -153,97 +158,19 @@ function buildRegistries(blueprint, serverData, appId) {
|
|
|
153
158
|
};
|
|
154
159
|
return { entities, workflows, scheduledJobs, agents, publicEndpoints, components, fileStorage };
|
|
155
160
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
description: 'Start local development with live sync, preview sandbox, and file watching',
|
|
170
|
-
usage: 'runwork dev [--json] [--no-logs]',
|
|
171
|
-
examples: ['runwork dev', 'runwork dev --json'],
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
name: 'deploy',
|
|
175
|
-
description: 'Deploy the current app to production',
|
|
176
|
-
usage: 'runwork deploy [--json]',
|
|
177
|
-
examples: ['runwork deploy', 'runwork deploy --json'],
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
name: 'logs',
|
|
181
|
-
description: 'View app logs and events from preview or production',
|
|
182
|
-
usage: 'runwork logs [--json] [--events] [--production] [--follow] [--type <type>] [--search <text>]',
|
|
183
|
-
examples: [
|
|
184
|
-
'runwork logs --json',
|
|
185
|
-
'runwork logs --events --json',
|
|
186
|
-
'runwork logs --events --type failed --json',
|
|
187
|
-
'runwork logs --production --json',
|
|
188
|
-
'runwork logs --follow --json',
|
|
189
|
-
],
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
name: 'open',
|
|
193
|
-
description: 'Open preview URL or dashboard in browser. In JSON mode, outputs the URL without opening the browser.',
|
|
194
|
-
usage: 'runwork open [preview|dashboard] [--json]',
|
|
195
|
-
examples: ['runwork open', 'runwork open dashboard', 'runwork open --json'],
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
name: 'integrations search',
|
|
199
|
-
description: 'Search available integrations from the platform catalog (3200+ integrations)',
|
|
200
|
-
usage: 'runwork integrations search <query> [--json] [--limit <n>]',
|
|
201
|
-
examples: [
|
|
202
|
-
'runwork integrations search stripe --json',
|
|
203
|
-
"runwork integrations search 'google drive' --json --limit 5",
|
|
204
|
-
],
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
name: 'integrations list',
|
|
208
|
-
description: 'Show integrations connected to the current workspace',
|
|
209
|
-
usage: 'runwork integrations list [--json]',
|
|
210
|
-
examples: ['runwork integrations list --json'],
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
name: 'login',
|
|
214
|
-
description: 'Authenticate with Runwork platform (interactive -- requires browser)',
|
|
215
|
-
usage: 'runwork login [--base-url <url>]',
|
|
216
|
-
examples: ['runwork login'],
|
|
217
|
-
note: 'Not available in JSON mode -- requires interactive browser OAuth flow',
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
name: 'init',
|
|
221
|
-
description: 'Initialize a new Runwork app (interactive)',
|
|
222
|
-
usage: 'runwork init [name]',
|
|
223
|
-
examples: ['runwork init my-app'],
|
|
224
|
-
note: 'Requires interactive prompts for workspace selection. Provide app name as argument to reduce prompts.',
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
name: 'clone',
|
|
228
|
-
description: 'Clone an existing Runwork app to a local directory for development',
|
|
229
|
-
usage: 'runwork clone [appId] [directory]',
|
|
230
|
-
examples: ['runwork clone abc-123 ./my-app'],
|
|
231
|
-
note: 'Provide appId argument to skip interactive selection',
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
name: 'upgrade',
|
|
235
|
-
description: 'Upgrade Runwork CLI to the latest version. Use --check to see if an update is available without installing.',
|
|
236
|
-
usage: 'runwork upgrade [--check]',
|
|
237
|
-
examples: ['runwork upgrade', 'runwork upgrade --check'],
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
name: 'logout',
|
|
241
|
-
description: 'Remove stored credentials and git credential configuration',
|
|
242
|
-
usage: 'runwork logout',
|
|
243
|
-
examples: ['runwork logout'],
|
|
244
|
-
},
|
|
245
|
-
];
|
|
246
|
-
function formatSources(sources) {
|
|
161
|
+
function getCliCommands(command) {
|
|
162
|
+
const program = command.parent;
|
|
163
|
+
if (!program)
|
|
164
|
+
return [];
|
|
165
|
+
return program.commands
|
|
166
|
+
.filter(cmd => !cmd._hidden)
|
|
167
|
+
.map(cmd => {
|
|
168
|
+
const aliases = cmd.aliases();
|
|
169
|
+
const name = aliases.length > 0 ? `${cmd.name()}|${aliases.join('|')}` : cmd.name();
|
|
170
|
+
return { name, description: cmd.description() };
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
export function formatSources(sources) {
|
|
247
174
|
return sources
|
|
248
175
|
.map(s => {
|
|
249
176
|
if (s === 'blueprint')
|
|
@@ -257,19 +184,22 @@ function formatSources(sources) {
|
|
|
257
184
|
.join(dim(', '));
|
|
258
185
|
}
|
|
259
186
|
function printHumanOutput(data) {
|
|
260
|
-
console.log('');
|
|
261
|
-
console.log(bold('Runwork App Info'));
|
|
262
187
|
console.log('');
|
|
263
188
|
const pad = (label) => label.padEnd(12);
|
|
264
|
-
console.log(` ${dim(pad('App:'))}${bold(data.app.name)}`);
|
|
265
189
|
console.log(` ${dim(pad('Workspace:'))}${data.workspace.name || data.workspace.id}`);
|
|
266
|
-
if (data.
|
|
267
|
-
console.log(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
console.log(` ${dim(pad('
|
|
190
|
+
if (data.app) {
|
|
191
|
+
console.log('');
|
|
192
|
+
console.log(bold('App Info'));
|
|
193
|
+
console.log('');
|
|
194
|
+
console.log(` ${dim(pad('App:'))}${bold(data.app.name)}`);
|
|
195
|
+
if (data.preview.url) {
|
|
196
|
+
console.log(` ${dim(pad('Preview:'))}${green(data.preview.url)} ${dim('(running)')}`);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.log(` ${dim(pad('Preview:'))}${dim('(not running)')}`);
|
|
200
|
+
}
|
|
201
|
+
console.log(` ${dim(pad('Production:'))}${dim('(not available)')}`);
|
|
271
202
|
}
|
|
272
|
-
console.log(` ${dim(pad('Production:'))}${dim('(not available)')}`);
|
|
273
203
|
console.log('');
|
|
274
204
|
// Integrations
|
|
275
205
|
if (data.integrations === null) {
|
|
@@ -368,25 +298,39 @@ export const infoCommand = new Command('info')
|
|
|
368
298
|
.description('Show app context, registries, and CLI reference (use --json for agent discovery)')
|
|
369
299
|
.action(async (_opts, command) => {
|
|
370
300
|
const asJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
371
|
-
const config = readConfig();
|
|
372
|
-
const blueprint = readBlueprint(process.cwd());
|
|
373
301
|
const creds = requireAuth();
|
|
374
302
|
const client = new ApiClient(creds);
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
303
|
+
// Try to read project config, but don't require it
|
|
304
|
+
const config = tryReadConfig();
|
|
305
|
+
const blueprint = config ? readBlueprint(process.cwd()) : null;
|
|
306
|
+
// Resolve workspace: project config > --workspace > default > prompt
|
|
307
|
+
let workspaceId;
|
|
308
|
+
let workspaceName;
|
|
309
|
+
if (config) {
|
|
310
|
+
workspaceId = config.workspaceId;
|
|
311
|
+
workspaceName = config.workspaceName ?? config.workspaceId;
|
|
382
312
|
}
|
|
383
|
-
|
|
384
|
-
|
|
313
|
+
else {
|
|
314
|
+
const resolved = await resolveWorkspace(client, {});
|
|
315
|
+
workspaceId = resolved.workspaceId;
|
|
316
|
+
workspaceName = resolved.workspaceName || resolved.workspaceId;
|
|
385
317
|
}
|
|
318
|
+
const appId = config?.appId;
|
|
319
|
+
const appName = config?.appName;
|
|
320
|
+
// App-specific info (preview, dashboard, git remote, server registries)
|
|
321
|
+
const appInfo = appId ? await fetchAppInfo(client, {
|
|
322
|
+
appId,
|
|
323
|
+
appName: appName ?? '',
|
|
324
|
+
appSlug: appName ?? '',
|
|
325
|
+
workspaceId,
|
|
326
|
+
workspaceName,
|
|
327
|
+
baseUrl: creds.baseUrl || 'https://runwork.ai',
|
|
328
|
+
}) : null;
|
|
329
|
+
const preview = appInfo?.preview ?? { url: null, active: false };
|
|
386
330
|
// Integrations
|
|
387
331
|
let integrations = null;
|
|
388
332
|
try {
|
|
389
|
-
const raw = await client.listConnectedIntegrations(
|
|
333
|
+
const raw = await client.listConnectedIntegrations(workspaceId);
|
|
390
334
|
integrations = raw.map(i => ({
|
|
391
335
|
id: i.canonicalId ?? i.integrationId,
|
|
392
336
|
status: i.status ?? 'connected',
|
|
@@ -398,21 +342,21 @@ export const infoCommand = new Command('info')
|
|
|
398
342
|
// Workspace registries
|
|
399
343
|
let serverData = null;
|
|
400
344
|
try {
|
|
401
|
-
serverData = await client.getWorkspaceAll(
|
|
345
|
+
serverData = await client.getWorkspaceAll(workspaceId);
|
|
402
346
|
}
|
|
403
347
|
catch {
|
|
404
348
|
process.stderr.write('Warning: failed to fetch workspace registries\n');
|
|
405
349
|
}
|
|
406
|
-
const registries = buildRegistries(blueprint, serverData,
|
|
350
|
+
const registries = buildRegistries(blueprint, serverData, appId ?? '');
|
|
407
351
|
const output = {
|
|
408
|
-
app: {
|
|
409
|
-
id:
|
|
410
|
-
name:
|
|
411
|
-
slug:
|
|
412
|
-
},
|
|
352
|
+
app: appId ? {
|
|
353
|
+
id: appId,
|
|
354
|
+
name: appName ?? '',
|
|
355
|
+
slug: appName ?? '',
|
|
356
|
+
} : null,
|
|
413
357
|
workspace: {
|
|
414
|
-
id:
|
|
415
|
-
name:
|
|
358
|
+
id: workspaceId,
|
|
359
|
+
name: workspaceName,
|
|
416
360
|
},
|
|
417
361
|
preview,
|
|
418
362
|
production: { url: null, deployed: false },
|
|
@@ -420,7 +364,7 @@ export const infoCommand = new Command('info')
|
|
|
420
364
|
registries,
|
|
421
365
|
cli: {
|
|
422
366
|
version: VERSION,
|
|
423
|
-
commands:
|
|
367
|
+
commands: getCliCommands(command),
|
|
424
368
|
},
|
|
425
369
|
};
|
|
426
370
|
if (asJson) {
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -2,4 +2,6 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { ApiClient } from '../api/client.js';
|
|
3
3
|
import type { WorkspaceInfo } from '../types.js';
|
|
4
4
|
export declare function execInit(client: ApiClient, appName: string, workspace: WorkspaceInfo): Promise<string>;
|
|
5
|
+
/** Full create flow: prompt for name/workspace, init, and run agent wizard */
|
|
6
|
+
export declare function runCreateFlow(name?: string): Promise<void>;
|
|
5
7
|
export declare const initCommand: Command;
|
package/dist/commands/init.js
CHANGED
|
@@ -66,13 +66,10 @@ export async function execInit(client, appName, workspace) {
|
|
|
66
66
|
}
|
|
67
67
|
execFileSync('git', ['push', '-u', 'runwork', 'main'], { cwd: dir, stdio: 'inherit' });
|
|
68
68
|
console.log(`\nApp "${app.name}" initialized in ${dir}/`);
|
|
69
|
-
console.log(`Remote: ${remoteUrl}`);
|
|
70
69
|
return resolve(dir);
|
|
71
70
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
.argument('[name]', 'App name')
|
|
75
|
-
.action(async (name) => {
|
|
71
|
+
/** Full create flow: prompt for name/workspace, init, and run agent wizard */
|
|
72
|
+
export async function runCreateFlow(name) {
|
|
76
73
|
const creds = requireAuth();
|
|
77
74
|
const client = new ApiClient(creds);
|
|
78
75
|
const appName = name || await promptInput('App name');
|
|
@@ -87,9 +84,14 @@ export const initCommand = new Command('init')
|
|
|
87
84
|
}
|
|
88
85
|
const workspaceChoice = await promptSelect('Select workspace:', workspaces.map(w => ({ label: w.name, value: w })));
|
|
89
86
|
const workspace = workspaceChoice.value;
|
|
90
|
-
console.log(`
|
|
87
|
+
console.log(`Creating "${appName}" in workspace "${workspace.name}"...`);
|
|
91
88
|
const dir = await execInit(client, appName, workspace);
|
|
92
|
-
const slug = dir.split('/').pop() || dir;
|
|
93
89
|
await runAgentWizard(dir);
|
|
94
|
-
|
|
90
|
+
}
|
|
91
|
+
export const initCommand = new Command('init')
|
|
92
|
+
.alias('create')
|
|
93
|
+
.description('Create a new Runwork app')
|
|
94
|
+
.argument('[name]', 'App name')
|
|
95
|
+
.action(async (name) => {
|
|
96
|
+
await runCreateFlow(name);
|
|
95
97
|
});
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
3
|
import { requireAuth } from '../auth/store.js';
|
|
3
4
|
import { ApiClient } from '../api/client.js';
|
|
4
|
-
import { readFileSync, existsSync } from 'fs';
|
|
5
5
|
import { shouldOutputJson, jsonOut } from '../utils/output.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
console.error('No .runwork.json found. Run `runwork init` first.');
|
|
9
|
-
process.exit(1);
|
|
10
|
-
}
|
|
11
|
-
return JSON.parse(readFileSync('.runwork.json', 'utf-8'));
|
|
12
|
-
}
|
|
6
|
+
import { resolveWorkspace } from '../workspace/resolve.js';
|
|
7
|
+
import { parseCurlToRequest } from '../utils/curl-parser.js';
|
|
13
8
|
const searchCommand = new Command('search')
|
|
14
9
|
.description('Search available integrations from the platform catalog')
|
|
15
10
|
.argument('<query>', 'Search query (e.g., "google drive", "hubspot", "slack")')
|
|
@@ -63,13 +58,14 @@ const searchCommand = new Command('search')
|
|
|
63
58
|
});
|
|
64
59
|
const listCommand = new Command('list')
|
|
65
60
|
.description('List connected workspace integrations')
|
|
66
|
-
.
|
|
61
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
62
|
+
.action(async (opts, command) => {
|
|
67
63
|
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
68
64
|
const credentials = requireAuth();
|
|
69
65
|
const client = new ApiClient(credentials);
|
|
70
|
-
const
|
|
66
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
71
67
|
try {
|
|
72
|
-
const integrations = await client.listConnectedIntegrations(
|
|
68
|
+
const integrations = await client.listConnectedIntegrations(workspaceId);
|
|
73
69
|
if (useJson) {
|
|
74
70
|
const items = integrations.map(i => ({
|
|
75
71
|
id: i.canonicalId || i.integrationId,
|
|
@@ -97,7 +93,104 @@ const listCommand = new Command('list')
|
|
|
97
93
|
process.exit(1);
|
|
98
94
|
}
|
|
99
95
|
});
|
|
96
|
+
const callCommand = new Command('call')
|
|
97
|
+
.description('Make a proxy call to a connected integration')
|
|
98
|
+
.argument('<integration>', 'Integration name (e.g., hubspot, slack)')
|
|
99
|
+
.argument('[method]', 'HTTP method (GET, POST, PUT, DELETE)')
|
|
100
|
+
.argument('[path]', 'API path (e.g., /crm/v3/contacts)')
|
|
101
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
102
|
+
.option('--body <json>', 'Request body JSON')
|
|
103
|
+
.option('--header <header>', 'Request header (repeatable)', (val, prev) => [...prev, val], [])
|
|
104
|
+
.option('--query <query>', 'Query string (e.g., limit=10&offset=0)')
|
|
105
|
+
.option('--curl <command>', 'Parse a curl command (paste from Chrome DevTools "Copy as cURL")')
|
|
106
|
+
.option('--curl-file <file>', 'Read curl command from a file')
|
|
107
|
+
.action(async (integration, method, path, opts, command) => {
|
|
108
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
109
|
+
const credentials = requireAuth();
|
|
110
|
+
const client = new ApiClient(credentials);
|
|
111
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
112
|
+
let finalMethod;
|
|
113
|
+
let finalPath;
|
|
114
|
+
const headers = {};
|
|
115
|
+
let body;
|
|
116
|
+
let query = opts.query;
|
|
117
|
+
// Mode 1: --curl or --curl-file
|
|
118
|
+
if (opts.curl || opts.curlFile) {
|
|
119
|
+
let curlStr = opts.curl;
|
|
120
|
+
if (opts.curlFile) {
|
|
121
|
+
const filePath = opts.curlFile;
|
|
122
|
+
if (!existsSync(filePath)) {
|
|
123
|
+
console.error(`File not found: ${filePath}`);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
curlStr = readFileSync(filePath, 'utf-8');
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const result = await parseCurlToRequest(curlStr);
|
|
130
|
+
finalMethod = result.method;
|
|
131
|
+
finalPath = result.path;
|
|
132
|
+
if (result.query)
|
|
133
|
+
query = query || result.query;
|
|
134
|
+
Object.assign(headers, result.headers);
|
|
135
|
+
body = result.body;
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
console.error('Failed to parse curl command:', err instanceof Error ? err.message : err);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Mode 2: explicit method + path args
|
|
143
|
+
else if (method && path) {
|
|
144
|
+
finalMethod = method.toUpperCase();
|
|
145
|
+
finalPath = path;
|
|
146
|
+
// Parse headers from repeatable --header flags
|
|
147
|
+
for (const h of (opts.header || [])) {
|
|
148
|
+
const [key, ...rest] = h.split(':');
|
|
149
|
+
headers[key.trim()] = rest.join(':').trim();
|
|
150
|
+
}
|
|
151
|
+
// Parse body
|
|
152
|
+
if (opts.body) {
|
|
153
|
+
try {
|
|
154
|
+
body = JSON.parse(opts.body);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
console.error('Invalid JSON in --body');
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
console.error('Usage: runwork integrations call <integration> <method> <path>');
|
|
164
|
+
console.error(' or: runwork integrations call <integration> --curl "curl ..."');
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
// Find the integration's database row ID from connected integrations
|
|
169
|
+
const integrations = await client.listConnectedIntegrations(workspaceId);
|
|
170
|
+
const match = integrations.find(i => (i.canonicalId || i.integrationId) === integration || i.provider === integration);
|
|
171
|
+
if (!match) {
|
|
172
|
+
console.error(`Integration "${integration}" not found. Run "runwork integrations list" to see connected integrations.`);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
const result = await client.callIntegrationProxy(match.integrationId, finalMethod, finalPath, { body, headers: Object.keys(headers).length > 0 ? headers : undefined, query });
|
|
176
|
+
if (useJson) {
|
|
177
|
+
jsonOut(result);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (typeof result === 'string') {
|
|
181
|
+
console.log(result);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
console.log(JSON.stringify(result, null, 2));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
console.error('Failed to call integration:', err instanceof Error ? err.message : err);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
100
192
|
export const integrationsCommand = new Command('integrations')
|
|
101
193
|
.description('Search and manage workspace integrations')
|
|
102
194
|
.addCommand(searchCommand)
|
|
103
|
-
.addCommand(listCommand)
|
|
195
|
+
.addCommand(listCommand)
|
|
196
|
+
.addCommand(callCommand);
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { requireAuth } from '../auth/store.js';
|
|
3
|
+
import { ApiClient } from '../api/client.js';
|
|
4
|
+
import { resolveWorkspace } from '../workspace/resolve.js';
|
|
5
|
+
import { promptConfirm } from '../utils/prompt.js';
|
|
6
|
+
import { shouldOutputJson, jsonOut } from '../utils/output.js';
|
|
7
|
+
const listCommand = new Command('list')
|
|
8
|
+
.description('List workspace MCP servers')
|
|
9
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
10
|
+
.action(async (opts, command) => {
|
|
11
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
12
|
+
const credentials = requireAuth();
|
|
13
|
+
const client = new ApiClient(credentials);
|
|
14
|
+
const { workspaceId, workspaceName } = await resolveWorkspace(client, opts);
|
|
15
|
+
try {
|
|
16
|
+
const servers = await client.listMcpServers(workspaceId);
|
|
17
|
+
if (useJson) {
|
|
18
|
+
jsonOut({ servers });
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (servers.length === 0) {
|
|
22
|
+
console.log('No MCP servers configured in this workspace.');
|
|
23
|
+
console.log('Add one with: runwork mcp add <name> <url>');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
console.log(`\nWorkspace: ${workspaceName || workspaceId}\n`);
|
|
27
|
+
console.log(` ${'Name'.padEnd(20)} ${'URL'.padEnd(45)} Status`);
|
|
28
|
+
console.log(' ' + '-'.repeat(75));
|
|
29
|
+
for (const s of servers) {
|
|
30
|
+
const status = s.enabled
|
|
31
|
+
? (s.toolCount ? `connected (${s.toolCount} tools)` : 'enabled')
|
|
32
|
+
: 'disabled';
|
|
33
|
+
console.log(` ${s.name.padEnd(20)} ${s.url.padEnd(45)} ${status}`);
|
|
34
|
+
}
|
|
35
|
+
console.log('');
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
console.error('Failed to list MCP servers:', err instanceof Error ? err.message : err);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const addCommand = new Command('add')
|
|
43
|
+
.description('Add an MCP server to workspace')
|
|
44
|
+
.argument('<name>', 'Server name')
|
|
45
|
+
.argument('<url>', 'Server URL (Streamable HTTP or SSE endpoint)')
|
|
46
|
+
.option('--transport <type>', 'Transport type: streamable-http or sse', 'streamable-http')
|
|
47
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
48
|
+
.action(async (name, url, opts, command) => {
|
|
49
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
50
|
+
const credentials = requireAuth();
|
|
51
|
+
const client = new ApiClient(credentials);
|
|
52
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
53
|
+
try {
|
|
54
|
+
new URL(url);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
console.error(`Invalid URL: ${url}`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const server = await client.addMcpServer(workspaceId, {
|
|
62
|
+
name,
|
|
63
|
+
url,
|
|
64
|
+
transport: opts.transport,
|
|
65
|
+
});
|
|
66
|
+
if (useJson) {
|
|
67
|
+
jsonOut({ server });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
console.log(`Added MCP server "${name}" (${url})`);
|
|
71
|
+
console.log('Run `runwork sync` to update your local agent configs.');
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
console.error('Failed to add MCP server:', err instanceof Error ? err.message : err);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
const removeCommand = new Command('remove')
|
|
79
|
+
.description('Remove an MCP server from workspace')
|
|
80
|
+
.argument('<name>', 'Server name')
|
|
81
|
+
.option('--yes', 'Skip confirmation')
|
|
82
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
83
|
+
.action(async (name, opts, command) => {
|
|
84
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
85
|
+
const credentials = requireAuth();
|
|
86
|
+
const client = new ApiClient(credentials);
|
|
87
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
88
|
+
try {
|
|
89
|
+
const servers = await client.listMcpServers(workspaceId);
|
|
90
|
+
const match = servers.find(s => s.name === name);
|
|
91
|
+
if (!match) {
|
|
92
|
+
console.error(`MCP server "${name}" not found in workspace.`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
if (!opts.yes && !useJson) {
|
|
96
|
+
const confirmed = await promptConfirm(`Remove MCP server "${name}" (${match.url})?`);
|
|
97
|
+
if (!confirmed)
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
await client.removeMcpServer(workspaceId, match.id);
|
|
101
|
+
if (useJson) {
|
|
102
|
+
jsonOut({ removed: true, name });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
console.log(`Removed MCP server "${name}".`);
|
|
106
|
+
console.log('Run `runwork sync` to update your local agent configs.');
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
console.error('Failed to remove MCP server:', err instanceof Error ? err.message : err);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
export const mcpCommand = new Command('mcp')
|
|
114
|
+
.description('Manage workspace MCP servers')
|
|
115
|
+
.addCommand(listCommand)
|
|
116
|
+
.addCommand(addCommand)
|
|
117
|
+
.addCommand(removeCommand);
|