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,221 @@
|
|
|
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 scheduled jobs in the workspace')
|
|
16
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
17
|
+
.option('--app <name|id>', '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 schedules = await client.listSchedules(workspaceId);
|
|
25
|
+
if (opts.app) {
|
|
26
|
+
const { appId } = await resolveApp(client, workspaceId, opts);
|
|
27
|
+
schedules = schedules.filter(s => s.appId === appId);
|
|
28
|
+
}
|
|
29
|
+
if (useJson) {
|
|
30
|
+
jsonOut({ schedules });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (schedules.length === 0) {
|
|
34
|
+
console.log('No scheduled jobs found in this workspace.');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
console.log(`\nWorkspace: ${workspaceName || workspaceId}\n`);
|
|
38
|
+
console.log(' ' +
|
|
39
|
+
'Name'.padEnd(24) +
|
|
40
|
+
'App'.padEnd(20) +
|
|
41
|
+
'Schedule'.padEnd(18) +
|
|
42
|
+
'Status'.padEnd(12) +
|
|
43
|
+
'Description');
|
|
44
|
+
console.log(' ' + '-'.repeat(90));
|
|
45
|
+
for (const s of schedules) {
|
|
46
|
+
const mode = s.deploymentMode ? ` (${s.deploymentMode})` : '';
|
|
47
|
+
const status = s.status ? s.status + mode : mode.trim() || '-';
|
|
48
|
+
console.log(' ' +
|
|
49
|
+
s.name.padEnd(24) +
|
|
50
|
+
(s.appName || s.appId).slice(0, 18).padEnd(20) +
|
|
51
|
+
(s.schedule || '-').padEnd(18) +
|
|
52
|
+
status.padEnd(12) +
|
|
53
|
+
truncate(s.description, 40));
|
|
54
|
+
}
|
|
55
|
+
console.log('');
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
console.error('Failed to list schedules:', err instanceof Error ? err.message : err);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const triggerCommand = new Command('trigger')
|
|
63
|
+
.description('Manually trigger a scheduled job')
|
|
64
|
+
.argument('<name>', 'Schedule name')
|
|
65
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
66
|
+
.option('--app <name|id>', 'App name or ID')
|
|
67
|
+
.option('--preview', 'Trigger in preview (sandbox) mode instead of production')
|
|
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
|
+
const deploymentMode = opts.preview ? 'preview' : 'production';
|
|
75
|
+
try {
|
|
76
|
+
const result = await client.triggerSchedule(workspaceId, {
|
|
77
|
+
scheduleName: name,
|
|
78
|
+
appId,
|
|
79
|
+
deploymentMode,
|
|
80
|
+
});
|
|
81
|
+
if (useJson) {
|
|
82
|
+
jsonOut({ result, scheduleName: name, appId, appName, deploymentMode });
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
console.log(`Triggered schedule "${name}" on app "${appName}" (${deploymentMode}).`);
|
|
86
|
+
if (result.message) {
|
|
87
|
+
console.log(result.message);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
console.error('Failed to trigger schedule:', err instanceof Error ? err.message : err);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
const historyCommand = new Command('history')
|
|
96
|
+
.description('Show execution history for scheduled jobs in an app')
|
|
97
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
98
|
+
.option('--app <name|id>', 'App name or ID (required)')
|
|
99
|
+
.action(async (opts, command) => {
|
|
100
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
101
|
+
const credentials = requireAuth();
|
|
102
|
+
const client = new ApiClient(credentials);
|
|
103
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
104
|
+
const { appId, appName } = await resolveApp(client, workspaceId, opts);
|
|
105
|
+
try {
|
|
106
|
+
const data = await client.getScheduleHistory(workspaceId, appId);
|
|
107
|
+
if (useJson) {
|
|
108
|
+
jsonOut({ history: data.history, appId, appName });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (!data.history || data.history.length === 0) {
|
|
112
|
+
console.log(`No schedule history found for app "${appName}".`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
console.log(`\nSchedule history for app: ${appName}\n`);
|
|
116
|
+
console.log(' ' +
|
|
117
|
+
'Schedule'.padEnd(24) +
|
|
118
|
+
'Executed At'.padEnd(26) +
|
|
119
|
+
'Status'.padEnd(12) +
|
|
120
|
+
'Duration');
|
|
121
|
+
console.log(' ' + '-'.repeat(72));
|
|
122
|
+
for (const entry of data.history) {
|
|
123
|
+
const duration = entry.durationMs != null ? `${entry.durationMs}ms` : '-';
|
|
124
|
+
console.log(' ' +
|
|
125
|
+
entry.scheduleName.padEnd(24) +
|
|
126
|
+
entry.executedAt.padEnd(26) +
|
|
127
|
+
entry.status.padEnd(12) +
|
|
128
|
+
duration);
|
|
129
|
+
if (entry.error) {
|
|
130
|
+
console.log(` Error: ${entry.error}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
console.log('');
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
console.error('Failed to get schedule history:', err instanceof Error ? err.message : err);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
const statusCommand = new Command('status')
|
|
141
|
+
.description('Show runtime status of scheduled jobs for an app')
|
|
142
|
+
.argument('<name>', 'Schedule name')
|
|
143
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
144
|
+
.option('--app <name|id>', 'App name or ID (required)')
|
|
145
|
+
.action(async (name, opts, command) => {
|
|
146
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
147
|
+
const credentials = requireAuth();
|
|
148
|
+
const client = new ApiClient(credentials);
|
|
149
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
150
|
+
const { appId, appName } = await resolveApp(client, workspaceId, opts);
|
|
151
|
+
try {
|
|
152
|
+
const data = await client.getScheduleStatus(workspaceId, appId);
|
|
153
|
+
if (useJson) {
|
|
154
|
+
jsonOut({ status: data, scheduleName: name, appId, appName });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
console.log(`\nSchedule "${name}" status for app "${appName}":\n`);
|
|
158
|
+
console.log(JSON.stringify(data, null, 2));
|
|
159
|
+
console.log('');
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
console.error('Failed to get schedule status:', err instanceof Error ? err.message : err);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
const pauseCommand = new Command('pause')
|
|
167
|
+
.description('Pause a scheduled job')
|
|
168
|
+
.argument('<name>', 'Schedule name')
|
|
169
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
170
|
+
.option('--app <name|id>', 'App name or ID (required)')
|
|
171
|
+
.action(async (name, opts, command) => {
|
|
172
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
173
|
+
const credentials = requireAuth();
|
|
174
|
+
const client = new ApiClient(credentials);
|
|
175
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
176
|
+
const { appId, appName } = await resolveApp(client, workspaceId, opts);
|
|
177
|
+
try {
|
|
178
|
+
await client.pauseSchedule(workspaceId, name, appId);
|
|
179
|
+
if (useJson) {
|
|
180
|
+
jsonOut({ success: true, scheduleName: name, appId, appName, action: 'paused' });
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
console.log(`Schedule "${name}" paused for app "${appName}".`);
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
console.error('Failed to pause schedule:', err instanceof Error ? err.message : err);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
const resumeCommand = new Command('resume')
|
|
191
|
+
.description('Resume a paused scheduled job')
|
|
192
|
+
.argument('<name>', 'Schedule name')
|
|
193
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
194
|
+
.option('--app <name|id>', 'App name or ID (required)')
|
|
195
|
+
.action(async (name, opts, command) => {
|
|
196
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
197
|
+
const credentials = requireAuth();
|
|
198
|
+
const client = new ApiClient(credentials);
|
|
199
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
200
|
+
const { appId, appName } = await resolveApp(client, workspaceId, opts);
|
|
201
|
+
try {
|
|
202
|
+
await client.resumeSchedule(workspaceId, name, appId);
|
|
203
|
+
if (useJson) {
|
|
204
|
+
jsonOut({ success: true, scheduleName: name, appId, appName, action: 'resumed' });
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
console.log(`Schedule "${name}" resumed for app "${appName}".`);
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
console.error('Failed to resume schedule:', err instanceof Error ? err.message : err);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
export const schedulesCommand = new Command('schedules')
|
|
215
|
+
.description('Manage workspace scheduled jobs')
|
|
216
|
+
.addCommand(listCommand)
|
|
217
|
+
.addCommand(triggerCommand)
|
|
218
|
+
.addCommand(historyCommand)
|
|
219
|
+
.addCommand(statusCommand)
|
|
220
|
+
.addCommand(pauseCommand)
|
|
221
|
+
.addCommand(resumeCommand);
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync } 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 { resolveWorkspace, hasProjectConfig } from '../workspace/resolve.js';
|
|
8
|
+
import { promptSelect, promptConfirm } from '../utils/prompt.js';
|
|
9
|
+
import { detectAgents, printNoAgentsMessage } from '../agents/detect.js';
|
|
10
|
+
import { RUNWORK_MCP_PREFIX } from '../agents/types.js';
|
|
11
|
+
export const setupCommand = new Command('setup')
|
|
12
|
+
.description('Configure local AI agents with workspace skills and MCP servers')
|
|
13
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
14
|
+
.option('--agent <slug>', 'Only configure a specific agent (e.g. claude-code, cursor)')
|
|
15
|
+
.option('--dry-run', 'Show what would be configured without writing files')
|
|
16
|
+
.action(async (opts) => {
|
|
17
|
+
const credentials = requireAuth();
|
|
18
|
+
const client = new ApiClient(credentials);
|
|
19
|
+
// 1. Resolve workspace
|
|
20
|
+
const { workspaceId, workspaceName } = await resolveWorkspace(client, opts);
|
|
21
|
+
console.log(`\nWorkspace: ${workspaceName || workspaceId}\n`);
|
|
22
|
+
// 2. Detect agents
|
|
23
|
+
let agents = await detectAgents();
|
|
24
|
+
if (agents.length === 0) {
|
|
25
|
+
printNoAgentsMessage();
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
// Filter by --agent flag
|
|
29
|
+
if (opts.agent) {
|
|
30
|
+
agents = agents.filter(a => a.slug === opts.agent);
|
|
31
|
+
if (agents.length === 0) {
|
|
32
|
+
console.error(`Agent "${opts.agent}" not detected on this machine.`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// Show detected agents and confirm
|
|
38
|
+
console.log('Detected agents:');
|
|
39
|
+
for (const a of agents) {
|
|
40
|
+
console.log(` - ${a.name}`);
|
|
41
|
+
}
|
|
42
|
+
console.log('');
|
|
43
|
+
const configureAll = await promptConfirm(`Configure all ${agents.length} agents?`);
|
|
44
|
+
if (!configureAll) {
|
|
45
|
+
const kept = [];
|
|
46
|
+
for (const a of agents) {
|
|
47
|
+
const include = await promptConfirm(` Include ${a.name}?`);
|
|
48
|
+
if (include)
|
|
49
|
+
kept.push(a);
|
|
50
|
+
}
|
|
51
|
+
agents = kept;
|
|
52
|
+
if (agents.length === 0) {
|
|
53
|
+
console.log('No agents selected.');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// 3. Determine scope
|
|
59
|
+
let scope = 'user';
|
|
60
|
+
if (hasProjectConfig()) {
|
|
61
|
+
const scopeChoices = [
|
|
62
|
+
{ label: 'This project only', value: 'project' },
|
|
63
|
+
{ label: 'Your user (global)', value: 'user' },
|
|
64
|
+
{ label: 'Both', value: 'both' },
|
|
65
|
+
];
|
|
66
|
+
const chosen = await promptSelect('Configure for:', scopeChoices);
|
|
67
|
+
scope = chosen.value;
|
|
68
|
+
}
|
|
69
|
+
// 4. Fetch workspace data
|
|
70
|
+
console.log('\nFetching workspace skills and MCP servers...');
|
|
71
|
+
const [allSkills, mcpServers, externalSkills] = await Promise.all([
|
|
72
|
+
client.listWorkspaceSkills(workspaceId),
|
|
73
|
+
client.listMcpServers(workspaceId),
|
|
74
|
+
client.listExternalSkills(workspaceId),
|
|
75
|
+
]);
|
|
76
|
+
// 5. Build MCP server entries
|
|
77
|
+
const wsPrefix = workspaceId.substring(0, 8);
|
|
78
|
+
const mcpEntries = mcpServers
|
|
79
|
+
.filter(s => s.enabled)
|
|
80
|
+
.map(s => ({
|
|
81
|
+
name: `${RUNWORK_MCP_PREFIX}${wsPrefix}-${s.name}`,
|
|
82
|
+
url: s.url,
|
|
83
|
+
transport: s.transport,
|
|
84
|
+
headers: { Authorization: `Bearer ${credentials.apiKey}` },
|
|
85
|
+
}));
|
|
86
|
+
// Also add the workspace MCP endpoint itself
|
|
87
|
+
const baseUrl = credentials.baseUrl || 'https://runwork.ai';
|
|
88
|
+
mcpEntries.push({
|
|
89
|
+
name: `${RUNWORK_MCP_PREFIX}${wsPrefix}-workspace`,
|
|
90
|
+
url: `${baseUrl}/api/workspaces/${workspaceId}/mcp`,
|
|
91
|
+
transport: 'streamable-http',
|
|
92
|
+
headers: { Authorization: `Bearer ${credentials.apiKey}` },
|
|
93
|
+
});
|
|
94
|
+
// 6. Build skill files — external skills go to all scopes
|
|
95
|
+
const baseSkillFiles = externalSkills.map(s => ({
|
|
96
|
+
name: s.name,
|
|
97
|
+
filename: s.name.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
|
|
98
|
+
content: s.content,
|
|
99
|
+
description: s.description,
|
|
100
|
+
}));
|
|
101
|
+
// Fetch all app skills for user scope
|
|
102
|
+
const allAppSkillFiles = [];
|
|
103
|
+
const appSkillEntries = allSkills.filter(s => s.type === 'app' && s.appId);
|
|
104
|
+
if (appSkillEntries.length > 0) {
|
|
105
|
+
console.log(`Fetching skills for ${appSkillEntries.length} app(s)...`);
|
|
106
|
+
const results = await Promise.all(appSkillEntries.map(async (s) => {
|
|
107
|
+
try {
|
|
108
|
+
const content = await client.getAppSkillContent(workspaceId, s.appId);
|
|
109
|
+
if (content && content.trim()) {
|
|
110
|
+
return {
|
|
111
|
+
name: s.name,
|
|
112
|
+
filename: s.name.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
|
|
113
|
+
content,
|
|
114
|
+
description: s.description || `App skill for ${s.name}`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch { /* skip */ }
|
|
119
|
+
return null;
|
|
120
|
+
}));
|
|
121
|
+
for (const r of results) {
|
|
122
|
+
if (r)
|
|
123
|
+
allAppSkillFiles.push(r);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// For project scope: only the current app's skill
|
|
127
|
+
let projectAppSkillFile = null;
|
|
128
|
+
if (hasProjectConfig()) {
|
|
129
|
+
try {
|
|
130
|
+
const config = JSON.parse(readFileSync('.runwork.json', 'utf-8'));
|
|
131
|
+
if (config.appId) {
|
|
132
|
+
projectAppSkillFile = allAppSkillFiles.find(s => s.filename === (config.appName || '').toLowerCase().replace(/[^a-z0-9]+/g, '-')) || null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch { /* ignore */ }
|
|
136
|
+
}
|
|
137
|
+
// Build scope-specific skill lists
|
|
138
|
+
const userSkillFiles = [...baseSkillFiles, ...allAppSkillFiles];
|
|
139
|
+
const projectSkillFiles = projectAppSkillFile
|
|
140
|
+
? [...baseSkillFiles, projectAppSkillFile]
|
|
141
|
+
: [...baseSkillFiles];
|
|
142
|
+
if (opts.dryRun) {
|
|
143
|
+
console.log('\n[Dry run] Would configure:\n');
|
|
144
|
+
console.log(` MCP servers: ${mcpEntries.length}`);
|
|
145
|
+
for (const e of mcpEntries)
|
|
146
|
+
console.log(` - ${e.name}: ${e.url}`);
|
|
147
|
+
console.log(` Skills (user): ${userSkillFiles.length}`);
|
|
148
|
+
for (const s of userSkillFiles)
|
|
149
|
+
console.log(` - ${s.name}`);
|
|
150
|
+
if (scope === 'project' || scope === 'both') {
|
|
151
|
+
console.log(` Skills (project): ${projectSkillFiles.length}`);
|
|
152
|
+
for (const s of projectSkillFiles)
|
|
153
|
+
console.log(` - ${s.name}`);
|
|
154
|
+
}
|
|
155
|
+
console.log(`\n Agents: ${agents.map(a => a.name).join(', ')}`);
|
|
156
|
+
console.log(` Scope: ${scope}`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// 7. Write configs
|
|
160
|
+
const scopes = scope === 'both' ? ['project', 'user'] : [scope];
|
|
161
|
+
for (const agent of agents) {
|
|
162
|
+
for (const s of scopes) {
|
|
163
|
+
const skillFiles = s === 'project' ? projectSkillFiles : userSkillFiles;
|
|
164
|
+
try {
|
|
165
|
+
if (agent.supportsMcpScope(s) && mcpEntries.length > 0) {
|
|
166
|
+
await agent.writeMcpServers(mcpEntries, s);
|
|
167
|
+
console.log(` [${agent.name}] MCP servers configured (${s})`);
|
|
168
|
+
}
|
|
169
|
+
if (agent.supportsSkills() && skillFiles.length > 0) {
|
|
170
|
+
await agent.writeSkills(skillFiles, s);
|
|
171
|
+
console.log(` [${agent.name}] Skills written (${s})`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
console.warn(` [${agent.name}] Failed (${s}): ${err instanceof Error ? err.message : err}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// 8. Save setup state
|
|
180
|
+
const state = {
|
|
181
|
+
workspaceId,
|
|
182
|
+
workspaceName: workspaceName || '',
|
|
183
|
+
configuredAgents: agents.map(a => a.slug),
|
|
184
|
+
scope,
|
|
185
|
+
lastSyncAt: new Date().toISOString(),
|
|
186
|
+
mcpServers: mcpEntries.map(e => e.name),
|
|
187
|
+
skills: userSkillFiles.map(s => s.name),
|
|
188
|
+
};
|
|
189
|
+
if (scope === 'project' || scope === 'both') {
|
|
190
|
+
mkdirSync('.runwork', { recursive: true });
|
|
191
|
+
writeFileSync('.runwork/setup.json', JSON.stringify(state, null, 2));
|
|
192
|
+
}
|
|
193
|
+
if (scope === 'user' || scope === 'both') {
|
|
194
|
+
const dir = join(homedir(), '.runwork');
|
|
195
|
+
mkdirSync(dir, { recursive: true });
|
|
196
|
+
writeFileSync(join(dir, 'setup.json'), JSON.stringify(state, null, 2));
|
|
197
|
+
}
|
|
198
|
+
console.log('\nSetup complete. Run `runwork sync` anytime to refresh.');
|
|
199
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { requireAuth } from '../auth/store.js';
|
|
4
|
+
import { ApiClient } from '../api/client.js';
|
|
5
|
+
import { resolveWorkspace } from '../workspace/resolve.js';
|
|
6
|
+
import { shouldOutputJson, jsonOut } from '../utils/output.js';
|
|
7
|
+
function truncate(text, max) {
|
|
8
|
+
if (!text)
|
|
9
|
+
return '';
|
|
10
|
+
const first = text.split('\n')[0];
|
|
11
|
+
if (first.length <= max)
|
|
12
|
+
return first;
|
|
13
|
+
return first.slice(0, max - 3) + '...';
|
|
14
|
+
}
|
|
15
|
+
const listCommand = new Command('list')
|
|
16
|
+
.description('List workspace skills (app, external, MCP-generated)')
|
|
17
|
+
.option('--workspace <id>', 'Workspace 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
|
+
const skills = await client.listWorkspaceSkills(workspaceId);
|
|
25
|
+
if (useJson) {
|
|
26
|
+
jsonOut({ skills });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (skills.length === 0) {
|
|
30
|
+
console.log('No skills found in this workspace.');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
console.log(`\nWorkspace: ${workspaceName || workspaceId}\n`);
|
|
34
|
+
const appSkills = skills.filter(s => s.type === 'app');
|
|
35
|
+
const externalSkills = skills.filter(s => s.type === 'external');
|
|
36
|
+
const mcpSkills = skills.filter(s => s.type === 'mcp');
|
|
37
|
+
if (appSkills.length > 0) {
|
|
38
|
+
console.log(' App Skills (auto-generated)');
|
|
39
|
+
console.log(' ' + '-'.repeat(40));
|
|
40
|
+
for (const s of appSkills) {
|
|
41
|
+
console.log(` ${s.name.padEnd(24)} ${truncate(s.description, 60)}`);
|
|
42
|
+
}
|
|
43
|
+
console.log('');
|
|
44
|
+
}
|
|
45
|
+
if (externalSkills.length > 0) {
|
|
46
|
+
console.log(' External Skills');
|
|
47
|
+
console.log(' ' + '-'.repeat(40));
|
|
48
|
+
for (const s of externalSkills) {
|
|
49
|
+
console.log(` ${s.name.padEnd(24)} ${truncate(s.description, 60)}`);
|
|
50
|
+
}
|
|
51
|
+
console.log('');
|
|
52
|
+
}
|
|
53
|
+
if (mcpSkills.length > 0) {
|
|
54
|
+
console.log(' MCP-Generated Skills');
|
|
55
|
+
console.log(' ' + '-'.repeat(40));
|
|
56
|
+
for (const s of mcpSkills) {
|
|
57
|
+
console.log(` ${s.name.padEnd(24)} ${truncate(s.description, 60)}`);
|
|
58
|
+
}
|
|
59
|
+
console.log('');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
console.error('Failed to list skills:', err instanceof Error ? err.message : err);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const pushCommand = new Command('push')
|
|
68
|
+
.description('Upload a local skill file to workspace (upsert by name)')
|
|
69
|
+
.argument('<file>', 'Path to skill .md file')
|
|
70
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
71
|
+
.action(async (file, opts, command) => {
|
|
72
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
73
|
+
const credentials = requireAuth();
|
|
74
|
+
const client = new ApiClient(credentials);
|
|
75
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
76
|
+
if (!existsSync(file)) {
|
|
77
|
+
console.error(`File not found: ${file}`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const content = readFileSync(file, 'utf-8');
|
|
82
|
+
// Parse frontmatter
|
|
83
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
84
|
+
let name = '';
|
|
85
|
+
let description = '';
|
|
86
|
+
let body = content;
|
|
87
|
+
if (frontmatterMatch) {
|
|
88
|
+
const fm = frontmatterMatch[1];
|
|
89
|
+
body = frontmatterMatch[2];
|
|
90
|
+
const nameMatch = fm.match(/^name:\s*(.+)$/m);
|
|
91
|
+
const descMatch = fm.match(/^description:\s*(.+)$/m);
|
|
92
|
+
if (nameMatch)
|
|
93
|
+
name = nameMatch[1].trim();
|
|
94
|
+
if (descMatch)
|
|
95
|
+
description = descMatch[1].trim();
|
|
96
|
+
}
|
|
97
|
+
if (!name) {
|
|
98
|
+
console.error('Skill file must have a "name" in YAML frontmatter:\n---\nname: my-skill\ndescription: What it does\n---');
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
// Check if skill already exists (upsert by name)
|
|
102
|
+
const existing = await client.listExternalSkills(workspaceId);
|
|
103
|
+
const match = existing.find(s => s.name === name);
|
|
104
|
+
if (match) {
|
|
105
|
+
const updated = await client.updateExternalSkill(workspaceId, match.id, {
|
|
106
|
+
name, description, content: body,
|
|
107
|
+
});
|
|
108
|
+
if (useJson) {
|
|
109
|
+
jsonOut({ skill: updated, action: 'updated' });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
console.log(`Updated skill "${name}" in workspace.`);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
const created = await client.importExternalSkill(workspaceId, {
|
|
116
|
+
name, description, content: body, importedFrom: 'cli',
|
|
117
|
+
});
|
|
118
|
+
if (useJson) {
|
|
119
|
+
jsonOut({ skill: created, action: 'created' });
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
console.log(`Created skill "${name}" in workspace.`);
|
|
123
|
+
}
|
|
124
|
+
console.log('Run `runwork sync` to update your local agent configs.');
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
console.error('Failed to push skill:', err instanceof Error ? err.message : err);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
export const skillsCommand = new Command('skills')
|
|
132
|
+
.description('Manage workspace skills')
|
|
133
|
+
.addCommand(listCommand)
|
|
134
|
+
.addCommand(pushCommand);
|