runwork 0.2.5 → 0.4.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/dist/api/client.d.ts +2 -1
- package/dist/api/client.js +4 -0
- package/dist/auth/login-flow.d.ts +10 -0
- package/dist/auth/login-flow.js +37 -0
- package/dist/commands/clone.d.ts +3 -0
- package/dist/commands/clone.js +32 -24
- package/dist/commands/deploy.js +24 -7
- package/dist/commands/dev.d.ts +5 -0
- package/dist/commands/dev.js +151 -45
- package/dist/commands/info.d.ts +2 -0
- package/dist/commands/info.js +432 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.js +29 -21
- package/dist/commands/integrations.js +19 -2
- package/dist/commands/login.js +10 -25
- package/dist/commands/logs.js +82 -23
- package/dist/commands/open.d.ts +2 -0
- package/dist/commands/open.js +53 -0
- package/dist/commands/welcome.d.ts +1 -0
- package/dist/commands/welcome.js +83 -0
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/git/__tests__/sync.test.js +10 -8
- package/dist/git/auto-commit.d.ts +5 -1
- package/dist/git/auto-commit.js +15 -9
- package/dist/git/sync.js +46 -5
- package/dist/index.js +22 -1
- package/dist/logs/__tests__/tailer-format.test.d.ts +1 -0
- package/dist/logs/__tests__/tailer-format.test.js +43 -0
- package/dist/logs/tailer.d.ts +4 -0
- package/dist/logs/tailer.js +58 -10
- package/dist/types.d.ts +59 -0
- package/dist/ui/__tests__/banner.test.d.ts +1 -0
- package/dist/ui/__tests__/banner.test.js +82 -0
- package/dist/ui/__tests__/colors.test.d.ts +1 -0
- package/dist/ui/__tests__/colors.test.js +22 -0
- package/dist/ui/__tests__/keyboard.test.d.ts +1 -0
- package/dist/ui/__tests__/keyboard.test.js +30 -0
- package/dist/ui/__tests__/status-line.test.d.ts +1 -0
- package/dist/ui/__tests__/status-line.test.js +54 -0
- package/dist/ui/banner.d.ts +29 -0
- package/dist/ui/banner.js +118 -0
- package/dist/ui/colors.d.ts +4 -0
- package/dist/ui/colors.js +7 -0
- package/dist/ui/keyboard.d.ts +12 -0
- package/dist/ui/keyboard.js +57 -0
- package/dist/ui/status-line.d.ts +6 -0
- package/dist/ui/status-line.js +53 -0
- package/dist/utils/__tests__/output.test.d.ts +1 -0
- package/dist/utils/__tests__/output.test.js +38 -0
- package/dist/utils/__tests__/prompt.test.js +23 -99
- package/dist/utils/output.d.ts +17 -0
- package/dist/utils/output.js +27 -0
- package/dist/utils/prompt.d.ts +1 -0
- package/dist/utils/prompt.js +29 -21
- package/package.json +4 -2
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { shouldOutputJson, jsonOut } from '../utils/output.js';
|
|
5
|
+
import { bold, cyan, dim, green, yellow, gray } from '../ui/colors.js';
|
|
6
|
+
import { VERSION } from '../generated/version.js';
|
|
7
|
+
import { requireAuth } from '../auth/store.js';
|
|
8
|
+
import { ApiClient } from '../api/client.js';
|
|
9
|
+
function readConfig() {
|
|
10
|
+
if (!existsSync('.runwork.json')) {
|
|
11
|
+
console.error('No .runwork.json found. Run `runwork init` first.');
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
return JSON.parse(readFileSync('.runwork.json', 'utf-8'));
|
|
15
|
+
}
|
|
16
|
+
function readBlueprint(cwd) {
|
|
17
|
+
const blueprintPath = join(cwd, 'blueprint.json');
|
|
18
|
+
if (!existsSync(blueprintPath))
|
|
19
|
+
return null;
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(readFileSync(blueprintPath, 'utf-8'));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function mergeSources(blueprintItems, serverItems, getServerName, appId) {
|
|
28
|
+
const map = new Map();
|
|
29
|
+
for (const bp of blueprintItems) {
|
|
30
|
+
map.set(bp.name, { sources: ['blueprint'], blueprintExtra: bp.extra });
|
|
31
|
+
}
|
|
32
|
+
for (const serverItem of serverItems) {
|
|
33
|
+
if (serverItem.appId !== appId)
|
|
34
|
+
continue;
|
|
35
|
+
const name = getServerName(serverItem);
|
|
36
|
+
const mode = serverItem.deploymentMode;
|
|
37
|
+
const source = mode === 'production' ? 'production' : 'preview';
|
|
38
|
+
const existing = map.get(name);
|
|
39
|
+
if (existing) {
|
|
40
|
+
existing.sources.push(source);
|
|
41
|
+
existing.serverItem = serverItem;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
map.set(name, { sources: [source], serverItem: serverItem });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return map;
|
|
48
|
+
}
|
|
49
|
+
function buildRegistries(blueprint, serverData, appId) {
|
|
50
|
+
// Entities
|
|
51
|
+
const bpEntities = (blueprint?.entities ?? []).map(e => ({
|
|
52
|
+
name: e.entityName,
|
|
53
|
+
extra: e.schema ? { schema: e.schema } : {},
|
|
54
|
+
}));
|
|
55
|
+
const entityMap = mergeSources(bpEntities, serverData?.entities ?? [], (e) => e.entityName, appId);
|
|
56
|
+
const entities = Array.from(entityMap.entries()).map(([name, v]) => ({
|
|
57
|
+
name,
|
|
58
|
+
sources: v.sources,
|
|
59
|
+
...(v.blueprintExtra?.schema ? { schema: v.blueprintExtra.schema } :
|
|
60
|
+
v.serverItem && 'schema' in v.serverItem && v.serverItem.schema ? { schema: v.serverItem.schema } : {}),
|
|
61
|
+
}));
|
|
62
|
+
// Scheduled jobs (blueprint may use either key)
|
|
63
|
+
const bpSchedules = (blueprint?.scheduledJobs ?? blueprint?.schedules ?? []).map(s => ({
|
|
64
|
+
name: s.name,
|
|
65
|
+
extra: { schedule: s.schedule, description: s.description },
|
|
66
|
+
}));
|
|
67
|
+
const scheduleMap = mergeSources(bpSchedules, serverData?.schedules ?? [], (s) => s.name, appId);
|
|
68
|
+
const scheduledJobs = Array.from(scheduleMap.entries()).map(([name, v]) => {
|
|
69
|
+
const serverItem = v.serverItem;
|
|
70
|
+
return {
|
|
71
|
+
name,
|
|
72
|
+
sources: v.sources,
|
|
73
|
+
schedule: v.blueprintExtra?.schedule ?? serverItem?.schedule,
|
|
74
|
+
description: v.blueprintExtra?.description ?? serverItem?.description,
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
// Workflows
|
|
78
|
+
const bpWorkflows = (blueprint?.workflows ?? []).map(w => ({
|
|
79
|
+
name: w.name,
|
|
80
|
+
extra: { description: w.description },
|
|
81
|
+
}));
|
|
82
|
+
const workflowMap = mergeSources(bpWorkflows, serverData?.workflows ?? [], (w) => w.name, appId);
|
|
83
|
+
const workflows = Array.from(workflowMap.entries()).map(([name, v]) => {
|
|
84
|
+
const serverItem = v.serverItem;
|
|
85
|
+
return {
|
|
86
|
+
name,
|
|
87
|
+
sources: v.sources,
|
|
88
|
+
description: v.blueprintExtra?.description ?? serverItem?.description,
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
// Agents
|
|
92
|
+
const bpAgents = (blueprint?.agents ?? []).map(a => ({
|
|
93
|
+
name: a.name,
|
|
94
|
+
extra: { type: a.type, description: a.description },
|
|
95
|
+
}));
|
|
96
|
+
const agentMap = mergeSources(bpAgents, serverData?.agents ?? [], (a) => a.name, appId);
|
|
97
|
+
const agents = Array.from(agentMap.entries()).map(([name, v]) => {
|
|
98
|
+
const serverItem = v.serverItem;
|
|
99
|
+
return {
|
|
100
|
+
name,
|
|
101
|
+
sources: v.sources,
|
|
102
|
+
type: v.blueprintExtra?.type ?? serverItem?.type,
|
|
103
|
+
description: v.blueprintExtra?.description ?? serverItem?.description,
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
// Public endpoints (blueprint may use either key)
|
|
107
|
+
const bpEndpoints = (blueprint?.publicEndpoints ?? blueprint?.endpoints ?? []).map(e => ({
|
|
108
|
+
name: `${e.method ?? 'ANY'} ${e.path}`,
|
|
109
|
+
extra: { path: e.path, method: e.method, description: e.description },
|
|
110
|
+
}));
|
|
111
|
+
const endpointMap = mergeSources(bpEndpoints, serverData?.endpoints ?? [], (e) => `${e.method ?? 'ANY'} ${e.path}`, appId);
|
|
112
|
+
const publicEndpoints = Array.from(endpointMap.entries()).map(([, v]) => {
|
|
113
|
+
const serverItem = v.serverItem;
|
|
114
|
+
return {
|
|
115
|
+
path: v.blueprintExtra?.path ?? serverItem?.path ?? '',
|
|
116
|
+
method: v.blueprintExtra?.method ?? serverItem?.method,
|
|
117
|
+
sources: v.sources,
|
|
118
|
+
description: v.blueprintExtra?.description ?? serverItem?.description,
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
// Components
|
|
122
|
+
const bpComponents = (blueprint?.components ?? []).map(c => ({
|
|
123
|
+
name: c.componentName,
|
|
124
|
+
extra: {},
|
|
125
|
+
}));
|
|
126
|
+
const componentMap = mergeSources(bpComponents, serverData?.components ?? [], (c) => c.componentName, appId);
|
|
127
|
+
const components = Array.from(componentMap.entries()).map(([name, v]) => ({
|
|
128
|
+
name,
|
|
129
|
+
sources: v.sources,
|
|
130
|
+
}));
|
|
131
|
+
// File storage
|
|
132
|
+
const bpHasStorage = blueprint
|
|
133
|
+
? typeof blueprint.fileStorage === 'boolean'
|
|
134
|
+
? blueprint.fileStorage
|
|
135
|
+
: typeof blueprint.fileStorage === 'object'
|
|
136
|
+
? (blueprint.fileStorage.enabled !== false)
|
|
137
|
+
: false
|
|
138
|
+
: false;
|
|
139
|
+
const serverStorages = (serverData?.fileStorages ?? []).filter(fs => fs.appId === appId);
|
|
140
|
+
const storageSources = [];
|
|
141
|
+
if (bpHasStorage)
|
|
142
|
+
storageSources.push('blueprint');
|
|
143
|
+
for (const ss of serverStorages) {
|
|
144
|
+
const mode = ss.deploymentMode;
|
|
145
|
+
if (mode === 'production')
|
|
146
|
+
storageSources.push('production');
|
|
147
|
+
else
|
|
148
|
+
storageSources.push('preview');
|
|
149
|
+
}
|
|
150
|
+
const fileStorage = {
|
|
151
|
+
enabled: bpHasStorage || serverStorages.length > 0,
|
|
152
|
+
sources: storageSources,
|
|
153
|
+
};
|
|
154
|
+
return { entities, workflows, scheduledJobs, agents, publicEndpoints, components, fileStorage };
|
|
155
|
+
}
|
|
156
|
+
const CLI_COMMANDS = [
|
|
157
|
+
{
|
|
158
|
+
name: 'info',
|
|
159
|
+
description: 'Show app context, registries, integrations, and CLI reference',
|
|
160
|
+
usage: 'runwork info [--json]',
|
|
161
|
+
examples: [
|
|
162
|
+
'runwork info --json',
|
|
163
|
+
"runwork info --json | jq '.registries.entities'",
|
|
164
|
+
"runwork info --json | jq '.preview.url'",
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'dev',
|
|
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) {
|
|
247
|
+
return sources
|
|
248
|
+
.map(s => {
|
|
249
|
+
if (s === 'blueprint')
|
|
250
|
+
return cyan(s);
|
|
251
|
+
if (s === 'preview')
|
|
252
|
+
return yellow(s);
|
|
253
|
+
if (s === 'production')
|
|
254
|
+
return green(s);
|
|
255
|
+
return s;
|
|
256
|
+
})
|
|
257
|
+
.join(dim(', '));
|
|
258
|
+
}
|
|
259
|
+
function printHumanOutput(data) {
|
|
260
|
+
console.log('');
|
|
261
|
+
console.log(bold('Runwork App Info'));
|
|
262
|
+
console.log('');
|
|
263
|
+
const pad = (label) => label.padEnd(12);
|
|
264
|
+
console.log(` ${dim(pad('App:'))}${bold(data.app.name)}`);
|
|
265
|
+
console.log(` ${dim(pad('Workspace:'))}${data.workspace.name || data.workspace.id}`);
|
|
266
|
+
if (data.preview.url) {
|
|
267
|
+
console.log(` ${dim(pad('Preview:'))}${green(data.preview.url)} ${dim('(running)')}`);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
console.log(` ${dim(pad('Preview:'))}${dim('(not running)')}`);
|
|
271
|
+
}
|
|
272
|
+
console.log(` ${dim(pad('Production:'))}${dim('(not available)')}`);
|
|
273
|
+
console.log('');
|
|
274
|
+
// Integrations
|
|
275
|
+
if (data.integrations === null) {
|
|
276
|
+
console.log(bold('Integrations:'));
|
|
277
|
+
console.log(` ${dim('(unavailable -- API error or not authenticated)')}`);
|
|
278
|
+
}
|
|
279
|
+
else if (data.integrations.length === 0) {
|
|
280
|
+
console.log(bold('Integrations:'));
|
|
281
|
+
console.log(` ${dim('(none connected)')}`);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
console.log(bold('Integrations:'));
|
|
285
|
+
for (const int of data.integrations) {
|
|
286
|
+
const statusColor = int.status === 'connected' ? green : yellow;
|
|
287
|
+
console.log(` ${int.id.padEnd(20)} ${statusColor(int.status)}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
console.log('');
|
|
291
|
+
// Registries
|
|
292
|
+
if (data.registries === null) {
|
|
293
|
+
// Skip entirely if unavailable
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
const reg = data.registries;
|
|
297
|
+
const hasAny = reg.entities.length > 0 || reg.workflows.length > 0 ||
|
|
298
|
+
reg.scheduledJobs.length > 0 || reg.agents.length > 0 ||
|
|
299
|
+
reg.publicEndpoints.length > 0 || reg.components.length > 0 ||
|
|
300
|
+
reg.fileStorage.enabled;
|
|
301
|
+
if (!hasAny) {
|
|
302
|
+
// No registries -- skip the section entirely
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
console.log(bold('Registries:'));
|
|
306
|
+
if (reg.entities.length > 0) {
|
|
307
|
+
console.log(` ${bold('Entities:')}`);
|
|
308
|
+
for (const e of reg.entities) {
|
|
309
|
+
console.log(` ${e.name.padEnd(24)} ${formatSources(e.sources)}`);
|
|
310
|
+
}
|
|
311
|
+
console.log('');
|
|
312
|
+
}
|
|
313
|
+
if (reg.workflows.length > 0) {
|
|
314
|
+
console.log(` ${bold('Workflows:')}`);
|
|
315
|
+
for (const w of reg.workflows) {
|
|
316
|
+
const desc = w.description ? ` ${dim(w.description)}` : '';
|
|
317
|
+
console.log(` ${w.name.padEnd(24)} ${formatSources(w.sources)}${desc}`);
|
|
318
|
+
}
|
|
319
|
+
console.log('');
|
|
320
|
+
}
|
|
321
|
+
if (reg.scheduledJobs.length > 0) {
|
|
322
|
+
console.log(` ${bold('Scheduled Jobs:')}`);
|
|
323
|
+
for (const s of reg.scheduledJobs) {
|
|
324
|
+
const schedule = s.schedule ? ` ${gray(s.schedule)}` : '';
|
|
325
|
+
console.log(` ${s.name.padEnd(24)} ${formatSources(s.sources)}${schedule}`);
|
|
326
|
+
}
|
|
327
|
+
console.log('');
|
|
328
|
+
}
|
|
329
|
+
if (reg.agents.length > 0) {
|
|
330
|
+
console.log(` ${bold('Agents:')}`);
|
|
331
|
+
for (const a of reg.agents) {
|
|
332
|
+
const type = a.type ? ` ${dim(a.type)}` : '';
|
|
333
|
+
console.log(` ${a.name.padEnd(24)} ${formatSources(a.sources)}${type}`);
|
|
334
|
+
}
|
|
335
|
+
console.log('');
|
|
336
|
+
}
|
|
337
|
+
if (reg.publicEndpoints.length > 0) {
|
|
338
|
+
console.log(` ${bold('Public Endpoints:')}`);
|
|
339
|
+
for (const e of reg.publicEndpoints) {
|
|
340
|
+
const label = e.method ? `${e.method} ${e.path}` : e.path;
|
|
341
|
+
console.log(` ${label.padEnd(24)} ${formatSources(e.sources)}`);
|
|
342
|
+
}
|
|
343
|
+
console.log('');
|
|
344
|
+
}
|
|
345
|
+
if (reg.components.length > 0) {
|
|
346
|
+
console.log(` ${bold('Components:')}`);
|
|
347
|
+
for (const c of reg.components) {
|
|
348
|
+
console.log(` ${c.name.padEnd(24)} ${formatSources(c.sources)}`);
|
|
349
|
+
}
|
|
350
|
+
console.log('');
|
|
351
|
+
}
|
|
352
|
+
if (reg.fileStorage.enabled) {
|
|
353
|
+
console.log(` ${bold('File Storage:')} ${formatSources(reg.fileStorage.sources)}`);
|
|
354
|
+
console.log('');
|
|
355
|
+
}
|
|
356
|
+
} // end hasAny
|
|
357
|
+
}
|
|
358
|
+
// CLI commands
|
|
359
|
+
console.log(bold('CLI Commands:'));
|
|
360
|
+
for (const cmd of data.cli.commands) {
|
|
361
|
+
console.log(` ${cyan(('runwork ' + cmd.name).padEnd(36))} ${dim(cmd.description)}`);
|
|
362
|
+
}
|
|
363
|
+
console.log('');
|
|
364
|
+
console.log(dim(` CLI version: ${data.cli.version}`));
|
|
365
|
+
console.log('');
|
|
366
|
+
}
|
|
367
|
+
export const infoCommand = new Command('info')
|
|
368
|
+
.description('Show app context, registries, and CLI reference (use --json for agent discovery)')
|
|
369
|
+
.action(async (_opts, command) => {
|
|
370
|
+
const asJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
371
|
+
const config = readConfig();
|
|
372
|
+
const blueprint = readBlueprint(process.cwd());
|
|
373
|
+
const creds = requireAuth();
|
|
374
|
+
const client = new ApiClient(creds);
|
|
375
|
+
// Preview status -- read-only, never starts a session
|
|
376
|
+
let preview = { url: null, active: false };
|
|
377
|
+
try {
|
|
378
|
+
const status = await client.getDevStatus(config.appId);
|
|
379
|
+
if (status.previewUrl) {
|
|
380
|
+
preview = { url: status.previewUrl, active: true };
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
// No active session -- that's fine
|
|
385
|
+
}
|
|
386
|
+
// Integrations
|
|
387
|
+
let integrations = null;
|
|
388
|
+
try {
|
|
389
|
+
const raw = await client.listConnectedIntegrations(config.workspaceId);
|
|
390
|
+
integrations = raw.map(i => ({
|
|
391
|
+
id: i.canonicalId ?? i.integrationId,
|
|
392
|
+
status: i.status ?? 'connected',
|
|
393
|
+
}));
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
process.stderr.write('Warning: failed to fetch integrations\n');
|
|
397
|
+
}
|
|
398
|
+
// Workspace registries
|
|
399
|
+
let serverData = null;
|
|
400
|
+
try {
|
|
401
|
+
serverData = await client.getWorkspaceAll(config.workspaceId);
|
|
402
|
+
}
|
|
403
|
+
catch {
|
|
404
|
+
process.stderr.write('Warning: failed to fetch workspace registries\n');
|
|
405
|
+
}
|
|
406
|
+
const registries = buildRegistries(blueprint, serverData, config.appId);
|
|
407
|
+
const output = {
|
|
408
|
+
app: {
|
|
409
|
+
id: config.appId,
|
|
410
|
+
name: config.appName,
|
|
411
|
+
slug: config.appName,
|
|
412
|
+
},
|
|
413
|
+
workspace: {
|
|
414
|
+
id: config.workspaceId,
|
|
415
|
+
name: config.workspaceName ?? config.workspaceId,
|
|
416
|
+
},
|
|
417
|
+
preview,
|
|
418
|
+
production: { url: null, deployed: false },
|
|
419
|
+
integrations,
|
|
420
|
+
registries,
|
|
421
|
+
cli: {
|
|
422
|
+
version: VERSION,
|
|
423
|
+
commands: CLI_COMMANDS,
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
if (asJson) {
|
|
427
|
+
jsonOut(output);
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
printHumanOutput(output);
|
|
431
|
+
}
|
|
432
|
+
});
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { ApiClient } from '../api/client.js';
|
|
3
|
+
import type { WorkspaceInfo } from '../types.js';
|
|
4
|
+
export declare function execInit(client: ApiClient, appName: string, workspace: WorkspaceInfo): Promise<string>;
|
|
2
5
|
export declare const initCommand: Command;
|
package/dist/commands/init.js
CHANGED
|
@@ -1,32 +1,15 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { execFileSync } from 'child_process';
|
|
3
3
|
import { writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
4
|
-
import { join } from 'path';
|
|
4
|
+
import { join, resolve } from 'path';
|
|
5
5
|
import { requireAuth } from '../auth/store.js';
|
|
6
6
|
import { ApiClient } from '../api/client.js';
|
|
7
7
|
import { promptSelect, promptInput } from '../utils/prompt.js';
|
|
8
8
|
import { generateManifest, saveManifest } from '../template/manifest.js';
|
|
9
9
|
import { extractZip } from '../utils/zip.js';
|
|
10
10
|
import { removeNestedGitDirs } from '../utils/fs.js';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
.argument('[name]', 'App name')
|
|
14
|
-
.action(async (name) => {
|
|
15
|
-
const creds = requireAuth();
|
|
16
|
-
const client = new ApiClient(creds);
|
|
17
|
-
const appName = name || await promptInput('App name');
|
|
18
|
-
if (!appName) {
|
|
19
|
-
console.error('App name is required.');
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
const workspaces = await client.listWorkspaces();
|
|
23
|
-
if (workspaces.length === 0) {
|
|
24
|
-
console.error('No workspaces found. Create one at runwork.ai first.');
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
const workspaceChoice = await promptSelect('Select workspace:', workspaces.map(w => ({ label: w.name, value: w })));
|
|
28
|
-
const workspace = workspaceChoice.value;
|
|
29
|
-
console.log(`Initializing "${appName}" in workspace "${workspace.name}"...`);
|
|
11
|
+
import { runAgentWizard } from '../ui/banner.js';
|
|
12
|
+
export async function execInit(client, appName, workspace) {
|
|
30
13
|
const app = await client.initApp(workspace.id, appName);
|
|
31
14
|
const slug = app.slug || appName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
32
15
|
const dir = slug;
|
|
@@ -52,6 +35,7 @@ export const initCommand = new Command('init')
|
|
|
52
35
|
// Write .runwork.json AFTER template extraction so it doesn't get overwritten
|
|
53
36
|
const config = {
|
|
54
37
|
workspaceId: workspace.id,
|
|
38
|
+
workspaceName: workspace.name,
|
|
55
39
|
appId: app.id,
|
|
56
40
|
appName: app.name,
|
|
57
41
|
};
|
|
@@ -83,5 +67,29 @@ export const initCommand = new Command('init')
|
|
|
83
67
|
execFileSync('git', ['push', '-u', 'runwork', 'main'], { cwd: dir, stdio: 'inherit' });
|
|
84
68
|
console.log(`\nApp "${app.name}" initialized in ${dir}/`);
|
|
85
69
|
console.log(`Remote: ${remoteUrl}`);
|
|
86
|
-
|
|
70
|
+
return resolve(dir);
|
|
71
|
+
}
|
|
72
|
+
export const initCommand = new Command('init')
|
|
73
|
+
.description('Initialize a new Runwork app')
|
|
74
|
+
.argument('[name]', 'App name')
|
|
75
|
+
.action(async (name) => {
|
|
76
|
+
const creds = requireAuth();
|
|
77
|
+
const client = new ApiClient(creds);
|
|
78
|
+
const appName = name || await promptInput('App name');
|
|
79
|
+
if (!appName) {
|
|
80
|
+
console.error('App name is required.');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
const workspaces = await client.listWorkspaces();
|
|
84
|
+
if (workspaces.length === 0) {
|
|
85
|
+
console.error('No workspaces found. Create one at runwork.ai first.');
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
const workspaceChoice = await promptSelect('Select workspace:', workspaces.map(w => ({ label: w.name, value: w })));
|
|
89
|
+
const workspace = workspaceChoice.value;
|
|
90
|
+
console.log(`Initializing "${appName}" in workspace "${workspace.name}"...`);
|
|
91
|
+
const dir = await execInit(client, appName, workspace);
|
|
92
|
+
const slug = dir.split('/').pop() || dir;
|
|
93
|
+
await runAgentWizard(dir);
|
|
94
|
+
console.log(`Next: cd ${slug} && runwork dev`);
|
|
87
95
|
});
|
|
@@ -2,6 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { requireAuth } from '../auth/store.js';
|
|
3
3
|
import { ApiClient } from '../api/client.js';
|
|
4
4
|
import { readFileSync, existsSync } from 'fs';
|
|
5
|
+
import { shouldOutputJson, jsonOut } from '../utils/output.js';
|
|
5
6
|
function readConfig() {
|
|
6
7
|
if (!existsSync('.runwork.json')) {
|
|
7
8
|
console.error('No .runwork.json found. Run `runwork init` first.');
|
|
@@ -13,12 +14,18 @@ const searchCommand = new Command('search')
|
|
|
13
14
|
.description('Search available integrations from the platform catalog')
|
|
14
15
|
.argument('<query>', 'Search query (e.g., "google drive", "hubspot", "slack")')
|
|
15
16
|
.option('--limit <n>', 'Maximum results to show', '20')
|
|
16
|
-
.action(async (query, opts) => {
|
|
17
|
+
.action(async (query, opts, command) => {
|
|
18
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
17
19
|
const credentials = requireAuth();
|
|
18
20
|
const client = new ApiClient(credentials);
|
|
19
21
|
const limit = parseInt(opts.limit, 10);
|
|
20
22
|
try {
|
|
21
23
|
const { results } = await client.searchIntegrations(query, limit);
|
|
24
|
+
if (useJson) {
|
|
25
|
+
const items = results.map(({ provider: _p, ...rest }) => rest);
|
|
26
|
+
jsonOut({ results: items });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
22
29
|
if (results.length === 0) {
|
|
23
30
|
console.log(`No integrations found for "${query}".`);
|
|
24
31
|
console.log('Try broader terms (e.g., "calendar" instead of "google calendar").');
|
|
@@ -56,12 +63,22 @@ const searchCommand = new Command('search')
|
|
|
56
63
|
});
|
|
57
64
|
const listCommand = new Command('list')
|
|
58
65
|
.description('List connected workspace integrations')
|
|
59
|
-
.action(async () => {
|
|
66
|
+
.action(async (_opts, command) => {
|
|
67
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
60
68
|
const credentials = requireAuth();
|
|
61
69
|
const client = new ApiClient(credentials);
|
|
62
70
|
const config = readConfig();
|
|
63
71
|
try {
|
|
64
72
|
const integrations = await client.listConnectedIntegrations(config.workspaceId);
|
|
73
|
+
if (useJson) {
|
|
74
|
+
const items = integrations.map(i => ({
|
|
75
|
+
id: i.canonicalId || i.integrationId,
|
|
76
|
+
status: i.status || 'connected',
|
|
77
|
+
createdAt: i.createdAt || null,
|
|
78
|
+
}));
|
|
79
|
+
jsonOut({ items });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
65
82
|
if (integrations.length === 0) {
|
|
66
83
|
console.log('No integrations connected in this workspace.');
|
|
67
84
|
console.log('Connect integrations at https://runwork.ai/workspace-settings');
|
package/dist/commands/login.js
CHANGED
|
@@ -1,32 +1,17 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { configureGitCredentials } from '../git/credentials.js';
|
|
2
|
+
import { performLogin } from '../auth/login-flow.js';
|
|
3
|
+
import { bold, cyan } from '../ui/colors.js';
|
|
5
4
|
export const loginCommand = new Command('login')
|
|
6
5
|
.description('Authenticate with Runwork platform')
|
|
7
6
|
.option('--base-url <url>', 'Platform URL', 'https://runwork.ai')
|
|
8
7
|
.action(async (options) => {
|
|
9
|
-
const client = new ApiClient({ apiKey: '', email: '', baseUrl: options.baseUrl });
|
|
10
8
|
console.log('Opening browser for authentication...');
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
console.log(`
|
|
16
|
-
console.log('
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const pollInterval = 5000;
|
|
20
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
21
|
-
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
22
|
-
const result = await client.pollLogin(sessionId);
|
|
23
|
-
if (result) {
|
|
24
|
-
saveCredentials(result);
|
|
25
|
-
await configureGitCredentials(result.baseUrl || options.baseUrl);
|
|
26
|
-
console.log(`Logged in as ${result.email}`);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
console.error('Login timed out. Please try again.');
|
|
31
|
-
process.exit(1);
|
|
9
|
+
await performLogin(options.baseUrl);
|
|
10
|
+
console.log('');
|
|
11
|
+
console.log(bold('What next?'));
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log(` ${cyan('runwork init')} Create a new app`);
|
|
14
|
+
console.log(` ${cyan('runwork clone')} Clone an existing app`);
|
|
15
|
+
console.log(` ${cyan('runwork dev')} Start developing (inside an app directory)`);
|
|
16
|
+
console.log('');
|
|
32
17
|
});
|