@mintlify/cli 4.0.1188 → 4.0.1190

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.
@@ -0,0 +1,34 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { authenticatedFetch } from '../authenticatedFetch.js';
11
+ import { API_URL } from '../constants.js';
12
+ function request(path, init) {
13
+ return __awaiter(this, void 0, void 0, function* () {
14
+ const res = yield authenticatedFetch(`${API_URL}/api/cli/workflows${path}`, Object.assign(Object.assign({}, init), { headers: Object.assign({ Accept: 'application/json' }, init === null || init === void 0 ? void 0 : init.headers) }));
15
+ if (!res.ok) {
16
+ const body = yield res.text().catch(() => '');
17
+ throw new Error(`API error (${res.status}): ${body || res.statusText}`);
18
+ }
19
+ return res.json();
20
+ });
21
+ }
22
+ export function createWorkflow(subdomain, body) {
23
+ return request(`/${encodeURIComponent(subdomain)}`, {
24
+ method: 'POST',
25
+ headers: { 'Content-Type': 'application/json' },
26
+ body: JSON.stringify(body),
27
+ });
28
+ }
29
+ export function listWorkflows(subdomain) {
30
+ return request(`/${encodeURIComponent(subdomain)}`);
31
+ }
32
+ export function deleteWorkflow(subdomain, workflowSchemaId) {
33
+ return request(`/${encodeURIComponent(subdomain)}/${encodeURIComponent(workflowSchemaId)}`, { method: 'DELETE' });
34
+ }
@@ -0,0 +1,267 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { jsx as _jsx } from "react/jsx-runtime";
11
+ import { addLog, ErrorLog, SpinnerLog, SuccessLog, removeLastLog } from '@mintlify/previewing';
12
+ import chalk from 'chalk';
13
+ import { Text } from 'ink';
14
+ import yaml from 'js-yaml';
15
+ import fs from 'node:fs/promises';
16
+ import path from 'node:path';
17
+ import { CMD_EXEC_PATH, terminate } from '../helpers.js';
18
+ import { subdomainMiddleware } from '../middlewares/subdomainMiddleware.js';
19
+ import { trackEvent } from '../telemetry/track.js';
20
+ import { createWorkflow, deleteWorkflow, listWorkflows } from './client.js';
21
+ import { WORKFLOW_TYPES, } from './types.js';
22
+ function resolveFormat(argv) {
23
+ return argv.format === 'json' ? 'json' : 'table';
24
+ }
25
+ function output(format, text) {
26
+ if (format === 'table') {
27
+ addLog(_jsx(Text, { children: text }));
28
+ }
29
+ else {
30
+ process.stdout.write(text + '\n');
31
+ }
32
+ }
33
+ function requireSubdomain(subdomain) {
34
+ if (!subdomain) {
35
+ throw new Error('No subdomain set. Pass --subdomain, or run `mint config set subdomain <name>`.');
36
+ }
37
+ return subdomain;
38
+ }
39
+ function readWorkflowFile(filePath) {
40
+ return __awaiter(this, void 0, void 0, function* () {
41
+ const resolved = path.isAbsolute(filePath) ? filePath : path.join(CMD_EXEC_PATH, filePath);
42
+ const contents = yield fs.readFile(resolved, 'utf-8');
43
+ if (/\.ya?ml$/i.test(resolved))
44
+ return yaml.load(contents);
45
+ return JSON.parse(contents);
46
+ });
47
+ }
48
+ function buildTriggerFromFlags(argv) {
49
+ var _a;
50
+ const repos = ((_a = argv.pushRepo) !== null && _a !== void 0 ? _a : []).map(String).filter(Boolean);
51
+ if (argv.cron && repos.length > 0) {
52
+ throw new Error('Specify either --cron or --push-repo, not both.');
53
+ }
54
+ if (argv.cron)
55
+ return { cron: argv.cron };
56
+ if (repos.length > 0)
57
+ return { push: repos.map((repo) => ({ repo })) };
58
+ throw new Error('A trigger is required. Pass --cron <expr> or --push-repo <owner/repo>.');
59
+ }
60
+ function buildBodyFromFlags(argv) {
61
+ var _a;
62
+ if (!argv.name) {
63
+ throw new Error('--name is required when --file is not provided.');
64
+ }
65
+ const type = argv.type;
66
+ if (type !== undefined && !WORKFLOW_TYPES.includes(type)) {
67
+ throw new Error(`Invalid --type "${type}". Valid types: ${WORKFLOW_TYPES.join(', ')}`);
68
+ }
69
+ const body = {
70
+ name: argv.name,
71
+ on: buildTriggerFromFlags(argv),
72
+ };
73
+ if (argv.prompt)
74
+ body.prompt = argv.prompt;
75
+ if (type)
76
+ body.type = type;
77
+ if (argv.automerge !== undefined)
78
+ body.automerge = argv.automerge;
79
+ const contextRepos = ((_a = argv.contextRepo) !== null && _a !== void 0 ? _a : []).map(String).filter(Boolean);
80
+ if (contextRepos.length > 0)
81
+ body.context = contextRepos.map((repo) => ({ repo }));
82
+ return body;
83
+ }
84
+ function describeTrigger(trigger) {
85
+ if ('cron' in trigger)
86
+ return `cron(${trigger.cron})`;
87
+ if ('push' in trigger)
88
+ return `push(${trigger.push.map((p) => p.repo).join(', ')})`;
89
+ return `composio(${trigger.composio.toolkit}:${trigger.composio.triggerSlug})`;
90
+ }
91
+ function renderWorkflow(workflow) {
92
+ const lines = [];
93
+ lines.push(chalk.bold(`\nWorkflow ${workflow.name}`));
94
+ lines.push(` ${chalk.dim('ID')} ${workflow._id}`);
95
+ lines.push(` ${chalk.dim('Status')} ${workflow.status}`);
96
+ if (workflow.type)
97
+ lines.push(` ${chalk.dim('Type')} ${workflow.type}`);
98
+ lines.push(` ${chalk.dim('Trigger')} ${describeTrigger(workflow.trigger)}`);
99
+ if (workflow.automerge !== undefined) {
100
+ lines.push(` ${chalk.dim('Automerge')} ${workflow.automerge}`);
101
+ }
102
+ return lines.join('\n');
103
+ }
104
+ function renderList(workflows) {
105
+ if (workflows.length === 0)
106
+ return 'No workflows found.';
107
+ const rows = workflows.map((w) => {
108
+ var _a;
109
+ return [
110
+ w._id,
111
+ w.name,
112
+ (_a = w.type) !== null && _a !== void 0 ? _a : '—',
113
+ describeTrigger(w.trigger),
114
+ w.status,
115
+ ];
116
+ });
117
+ const headers = ['ID', 'Name', 'Type', 'Trigger', 'Status'];
118
+ const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => String(r[i]).length)));
119
+ const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(' ');
120
+ return [chalk.bold(fmt(headers)), ...rows.map(fmt)].join('\n');
121
+ }
122
+ const withSubdomain = (yargs) => yargs.option('subdomain', {
123
+ type: 'string',
124
+ description: 'Documentation subdomain (default: mint config set subdomain)',
125
+ });
126
+ const withFormat = (yargs) => yargs.option('format', {
127
+ type: 'string',
128
+ choices: ['table', 'json'],
129
+ description: 'Output format (table=pretty, json=raw)',
130
+ });
131
+ export const workflowsBuilder = (yargs) => yargs
132
+ .middleware(subdomainMiddleware)
133
+ .command('create', 'Create a new workflow', (yargs) => withFormat(withSubdomain(yargs)
134
+ .option('name', { type: 'string', description: 'Workflow name' })
135
+ .option('prompt', { type: 'string', description: 'Workflow prompt' })
136
+ .option('type', {
137
+ type: 'string',
138
+ choices: WORKFLOW_TYPES,
139
+ description: 'Workflow type',
140
+ })
141
+ .option('cron', {
142
+ type: 'string',
143
+ description: 'Cron expression for a scheduled trigger',
144
+ })
145
+ .option('push-repo', {
146
+ type: 'string',
147
+ array: true,
148
+ description: 'Repository (owner/repo) for a push trigger (repeatable)',
149
+ })
150
+ .option('context-repo', {
151
+ type: 'string',
152
+ array: true,
153
+ description: 'Additional context repository (repeatable)',
154
+ })
155
+ .option('automerge', {
156
+ type: 'boolean',
157
+ description: 'Automatically merge PRs opened by this workflow',
158
+ })
159
+ .option('file', {
160
+ type: 'string',
161
+ description: 'Path to a JSON or YAML file with the full workflow body',
162
+ }))
163
+ .example('mint workflow create --name Translations --cron "0 6 * * *" --type translations', '')
164
+ .example('mint workflow create --file workflow.yaml', ''), (argv) => __awaiter(void 0, void 0, void 0, function* () {
165
+ const format = resolveFormat(argv);
166
+ try {
167
+ const subdomain = requireSubdomain(argv.subdomain);
168
+ const body = argv.file
169
+ ? (yield readWorkflowFile(argv.file))
170
+ : buildBodyFromFlags(argv);
171
+ if (format === 'table')
172
+ addLog(_jsx(SpinnerLog, { message: "Creating workflow..." }));
173
+ const created = yield createWorkflow(subdomain, body);
174
+ if (format === 'table')
175
+ removeLastLog();
176
+ void trackEvent('cli.workflow.created', {
177
+ subdomain,
178
+ type: created.type,
179
+ triggerKind: describeTrigger(created.trigger).split('(')[0],
180
+ });
181
+ if (format === 'json') {
182
+ output(format, JSON.stringify(created, null, 2));
183
+ }
184
+ else {
185
+ addLog(_jsx(SuccessLog, { message: `Created workflow ${created._id}` }));
186
+ output(format, renderWorkflow(created));
187
+ }
188
+ yield terminate(0);
189
+ }
190
+ catch (err) {
191
+ if (format === 'table')
192
+ removeLastLog();
193
+ const message = err instanceof Error ? err.message : 'unknown error';
194
+ if (format === 'json') {
195
+ process.stderr.write(`Error: ${message}\n`);
196
+ }
197
+ else {
198
+ addLog(_jsx(ErrorLog, { message: message }));
199
+ }
200
+ yield terminate(1);
201
+ }
202
+ }))
203
+ .command('list', 'List workflows for the current deployment', (yargs) => withFormat(withSubdomain(yargs)), (argv) => __awaiter(void 0, void 0, void 0, function* () {
204
+ const format = resolveFormat(argv);
205
+ try {
206
+ const subdomain = requireSubdomain(argv.subdomain);
207
+ if (format === 'table')
208
+ addLog(_jsx(SpinnerLog, { message: "Fetching workflows..." }));
209
+ const workflows = yield listWorkflows(subdomain);
210
+ if (format === 'table')
211
+ removeLastLog();
212
+ if (format === 'json') {
213
+ output(format, JSON.stringify(workflows, null, 2));
214
+ }
215
+ else {
216
+ output(format, renderList(workflows));
217
+ }
218
+ yield terminate(0);
219
+ }
220
+ catch (err) {
221
+ if (format === 'table')
222
+ removeLastLog();
223
+ const message = err instanceof Error ? err.message : 'unknown error';
224
+ if (format === 'json') {
225
+ process.stderr.write(`Error: ${message}\n`);
226
+ }
227
+ else {
228
+ addLog(_jsx(ErrorLog, { message: message }));
229
+ }
230
+ yield terminate(1);
231
+ }
232
+ }))
233
+ .command('delete <id>', 'Delete a workflow by ID', (yargs) => withFormat(withSubdomain(yargs)).positional('id', {
234
+ type: 'string',
235
+ demandOption: true,
236
+ description: 'Workflow schema ID',
237
+ }), (argv) => __awaiter(void 0, void 0, void 0, function* () {
238
+ const format = resolveFormat(argv);
239
+ try {
240
+ const subdomain = requireSubdomain(argv.subdomain);
241
+ if (format === 'table')
242
+ addLog(_jsx(SpinnerLog, { message: "Deleting workflow..." }));
243
+ yield deleteWorkflow(subdomain, argv.id);
244
+ if (format === 'table')
245
+ removeLastLog();
246
+ if (format === 'json') {
247
+ output(format, JSON.stringify({ success: true, id: argv.id }, null, 2));
248
+ }
249
+ else {
250
+ addLog(_jsx(SuccessLog, { message: `Deleted workflow ${argv.id}` }));
251
+ }
252
+ yield terminate(0);
253
+ }
254
+ catch (err) {
255
+ if (format === 'table')
256
+ removeLastLog();
257
+ const message = err instanceof Error ? err.message : 'unknown error';
258
+ if (format === 'json') {
259
+ process.stderr.write(`Error: ${message}\n`);
260
+ }
261
+ else {
262
+ addLog(_jsx(ErrorLog, { message: message }));
263
+ }
264
+ yield terminate(1);
265
+ }
266
+ }))
267
+ .demandCommand(1, 'specify a subcommand: create, list, or delete');
@@ -0,0 +1,11 @@
1
+ export const WORKFLOW_TYPES = [
2
+ 'changelog',
3
+ 'source-code-agent',
4
+ 'translations',
5
+ 'writing-style',
6
+ 'typo-check',
7
+ 'broken-link-detection',
8
+ 'seo-metadata-audit',
9
+ 'assistant-docs-updates',
10
+ 'contextual-feedback-docs-updates',
11
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mintlify/cli",
3
- "version": "4.0.1188",
3
+ "version": "4.0.1190",
4
4
  "description": "The Mintlify CLI",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -45,12 +45,12 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@inquirer/prompts": "7.9.0",
48
- "@mintlify/common": "1.0.916",
49
- "@mintlify/link-rot": "3.0.1096",
48
+ "@mintlify/common": "1.0.917",
49
+ "@mintlify/link-rot": "3.0.1097",
50
50
  "@mintlify/models": "0.0.314",
51
- "@mintlify/prebuild": "1.0.1060",
52
- "@mintlify/previewing": "4.0.1121",
53
- "@mintlify/validation": "0.1.714",
51
+ "@mintlify/prebuild": "1.0.1061",
52
+ "@mintlify/previewing": "4.0.1122",
53
+ "@mintlify/validation": "0.1.715",
54
54
  "adm-zip": "0.5.16",
55
55
  "chalk": "5.2.0",
56
56
  "color": "4.2.3",
@@ -92,5 +92,5 @@
92
92
  "vitest": "2.1.9",
93
93
  "vitest-mock-process": "1.0.4"
94
94
  },
95
- "gitHead": "7a34f2266cde3f717b4963e830cd3668f70e8328"
95
+ "gitHead": "09f3191f9605985e677a588f75c5bd5f0cac420f"
96
96
  }
package/src/cli.tsx CHANGED
@@ -44,6 +44,7 @@ import { scoreHandler } from './score/index.js';
44
44
  import { status, getCliSubdomains } from './status.js';
45
45
  import { trackTelemetryPreferenceChange } from './telemetry/track.js';
46
46
  import { update } from './update.js';
47
+ import { workflowsBuilder } from './workflows/index.js';
47
48
 
48
49
  export const cli = ({ packageName = 'mint' }: { packageName?: string }) => {
49
50
  const telemetryMiddleware = createTelemetryMiddleware();
@@ -578,6 +579,7 @@ export const cli = ({ packageName = 'mint' }: { packageName?: string }) => {
578
579
  }
579
580
  )
580
581
  .command('analytics', 'View analytics for your documentation', analyticsBuilder)
582
+ .command('workflow', 'Create and manage workflows', workflowsBuilder)
581
583
  .command(
582
584
  'score [url]',
583
585
  'Run agent readiness checks on a docs site',
@@ -0,0 +1,39 @@
1
+ import { authenticatedFetch } from '../authenticatedFetch.js';
2
+ import { API_URL } from '../constants.js';
3
+ import type { Workflow, WorkflowBody } from './types.js';
4
+
5
+ async function request<T>(path: string, init?: RequestInit): Promise<T> {
6
+ const res = await authenticatedFetch(`${API_URL}/api/cli/workflows${path}`, {
7
+ ...init,
8
+ headers: { Accept: 'application/json', ...init?.headers },
9
+ });
10
+
11
+ if (!res.ok) {
12
+ const body = await res.text().catch(() => '');
13
+ throw new Error(`API error (${res.status}): ${body || res.statusText}`);
14
+ }
15
+
16
+ return res.json() as Promise<T>;
17
+ }
18
+
19
+ export function createWorkflow(subdomain: string, body: WorkflowBody): Promise<Workflow> {
20
+ return request<Workflow>(`/${encodeURIComponent(subdomain)}`, {
21
+ method: 'POST',
22
+ headers: { 'Content-Type': 'application/json' },
23
+ body: JSON.stringify(body),
24
+ });
25
+ }
26
+
27
+ export function listWorkflows(subdomain: string): Promise<Workflow[]> {
28
+ return request<Workflow[]>(`/${encodeURIComponent(subdomain)}`);
29
+ }
30
+
31
+ export function deleteWorkflow(
32
+ subdomain: string,
33
+ workflowSchemaId: string
34
+ ): Promise<{ success: boolean }> {
35
+ return request<{ success: boolean }>(
36
+ `/${encodeURIComponent(subdomain)}/${encodeURIComponent(workflowSchemaId)}`,
37
+ { method: 'DELETE' }
38
+ );
39
+ }
@@ -0,0 +1,286 @@
1
+ import { addLog, ErrorLog, SpinnerLog, SuccessLog, removeLastLog } from '@mintlify/previewing';
2
+ import chalk from 'chalk';
3
+ import { Text } from 'ink';
4
+ import yaml from 'js-yaml';
5
+ import fs from 'node:fs/promises';
6
+ import path from 'node:path';
7
+ import type { Argv } from 'yargs';
8
+
9
+ import { CMD_EXEC_PATH, terminate } from '../helpers.js';
10
+ import { subdomainMiddleware } from '../middlewares/subdomainMiddleware.js';
11
+ import { trackEvent } from '../telemetry/track.js';
12
+ import { createWorkflow, deleteWorkflow, listWorkflows } from './client.js';
13
+ import {
14
+ WORKFLOW_TYPES,
15
+ type Workflow,
16
+ type WorkflowBody,
17
+ type WorkflowTrigger,
18
+ type WorkflowType,
19
+ } from './types.js';
20
+
21
+ type CreateArgs = {
22
+ subdomain?: string;
23
+ name?: string;
24
+ prompt?: string;
25
+ type?: string;
26
+ cron?: string;
27
+ pushRepo?: (string | number)[];
28
+ contextRepo?: (string | number)[];
29
+ automerge?: boolean;
30
+ file?: string;
31
+ format?: string;
32
+ };
33
+
34
+ type OutputFormat = 'table' | 'json';
35
+
36
+ function resolveFormat(argv: { format?: string }): OutputFormat {
37
+ return argv.format === 'json' ? 'json' : 'table';
38
+ }
39
+
40
+ function output(format: OutputFormat, text: string) {
41
+ if (format === 'table') {
42
+ addLog(<Text>{text}</Text>);
43
+ } else {
44
+ process.stdout.write(text + '\n');
45
+ }
46
+ }
47
+
48
+ function requireSubdomain(subdomain: string | undefined): string {
49
+ if (!subdomain) {
50
+ throw new Error(
51
+ 'No subdomain set. Pass --subdomain, or run `mint config set subdomain <name>`.'
52
+ );
53
+ }
54
+ return subdomain;
55
+ }
56
+
57
+ async function readWorkflowFile(filePath: string): Promise<unknown> {
58
+ const resolved = path.isAbsolute(filePath) ? filePath : path.join(CMD_EXEC_PATH, filePath);
59
+ const contents = await fs.readFile(resolved, 'utf-8');
60
+ if (/\.ya?ml$/i.test(resolved)) return yaml.load(contents);
61
+ return JSON.parse(contents);
62
+ }
63
+
64
+ function buildTriggerFromFlags(argv: CreateArgs): WorkflowTrigger {
65
+ const repos = (argv.pushRepo ?? []).map(String).filter(Boolean);
66
+ if (argv.cron && repos.length > 0) {
67
+ throw new Error('Specify either --cron or --push-repo, not both.');
68
+ }
69
+ if (argv.cron) return { cron: argv.cron };
70
+ if (repos.length > 0) return { push: repos.map((repo) => ({ repo })) };
71
+ throw new Error('A trigger is required. Pass --cron <expr> or --push-repo <owner/repo>.');
72
+ }
73
+
74
+ function buildBodyFromFlags(argv: CreateArgs): WorkflowBody {
75
+ if (!argv.name) {
76
+ throw new Error('--name is required when --file is not provided.');
77
+ }
78
+ const type = argv.type;
79
+ if (type !== undefined && !WORKFLOW_TYPES.includes(type as WorkflowType)) {
80
+ throw new Error(`Invalid --type "${type}". Valid types: ${WORKFLOW_TYPES.join(', ')}`);
81
+ }
82
+ const body: WorkflowBody = {
83
+ name: argv.name,
84
+ on: buildTriggerFromFlags(argv),
85
+ };
86
+ if (argv.prompt) body.prompt = argv.prompt;
87
+ if (type) body.type = type as WorkflowType;
88
+ if (argv.automerge !== undefined) body.automerge = argv.automerge;
89
+ const contextRepos = (argv.contextRepo ?? []).map(String).filter(Boolean);
90
+ if (contextRepos.length > 0) body.context = contextRepos.map((repo) => ({ repo }));
91
+ return body;
92
+ }
93
+
94
+ function describeTrigger(trigger: WorkflowTrigger): string {
95
+ if ('cron' in trigger) return `cron(${trigger.cron})`;
96
+ if ('push' in trigger) return `push(${trigger.push.map((p) => p.repo).join(', ')})`;
97
+ return `composio(${trigger.composio.toolkit}:${trigger.composio.triggerSlug})`;
98
+ }
99
+
100
+ function renderWorkflow(workflow: Workflow): string {
101
+ const lines: string[] = [];
102
+ lines.push(chalk.bold(`\nWorkflow ${workflow.name}`));
103
+ lines.push(` ${chalk.dim('ID')} ${workflow._id}`);
104
+ lines.push(` ${chalk.dim('Status')} ${workflow.status}`);
105
+ if (workflow.type) lines.push(` ${chalk.dim('Type')} ${workflow.type}`);
106
+ lines.push(` ${chalk.dim('Trigger')} ${describeTrigger(workflow.trigger)}`);
107
+ if (workflow.automerge !== undefined) {
108
+ lines.push(` ${chalk.dim('Automerge')} ${workflow.automerge}`);
109
+ }
110
+ return lines.join('\n');
111
+ }
112
+
113
+ function renderList(workflows: Workflow[]): string {
114
+ if (workflows.length === 0) return 'No workflows found.';
115
+ const rows = workflows.map((w) => [
116
+ w._id,
117
+ w.name,
118
+ w.type ?? '—',
119
+ describeTrigger(w.trigger),
120
+ w.status,
121
+ ]);
122
+ const headers = ['ID', 'Name', 'Type', 'Trigger', 'Status'];
123
+ const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => String(r[i]).length)));
124
+ const fmt = (cells: string[]) => cells.map((c, i) => c.padEnd(widths[i]!)).join(' ');
125
+ return [chalk.bold(fmt(headers)), ...rows.map(fmt)].join('\n');
126
+ }
127
+
128
+ const withSubdomain = <T extends object>(yargs: Argv<T>) =>
129
+ yargs.option('subdomain', {
130
+ type: 'string' as const,
131
+ description: 'Documentation subdomain (default: mint config set subdomain)',
132
+ });
133
+
134
+ const withFormat = <T extends object>(yargs: Argv<T>) =>
135
+ yargs.option('format', {
136
+ type: 'string' as const,
137
+ choices: ['table', 'json'] as const,
138
+ description: 'Output format (table=pretty, json=raw)',
139
+ });
140
+
141
+ export const workflowsBuilder = (yargs: Argv) =>
142
+ yargs
143
+ .middleware(subdomainMiddleware)
144
+ .command(
145
+ 'create',
146
+ 'Create a new workflow',
147
+ (yargs) =>
148
+ withFormat(
149
+ withSubdomain(yargs)
150
+ .option('name', { type: 'string', description: 'Workflow name' })
151
+ .option('prompt', { type: 'string', description: 'Workflow prompt' })
152
+ .option('type', {
153
+ type: 'string',
154
+ choices: WORKFLOW_TYPES,
155
+ description: 'Workflow type',
156
+ })
157
+ .option('cron', {
158
+ type: 'string',
159
+ description: 'Cron expression for a scheduled trigger',
160
+ })
161
+ .option('push-repo', {
162
+ type: 'string',
163
+ array: true,
164
+ description: 'Repository (owner/repo) for a push trigger (repeatable)',
165
+ })
166
+ .option('context-repo', {
167
+ type: 'string',
168
+ array: true,
169
+ description: 'Additional context repository (repeatable)',
170
+ })
171
+ .option('automerge', {
172
+ type: 'boolean',
173
+ description: 'Automatically merge PRs opened by this workflow',
174
+ })
175
+ .option('file', {
176
+ type: 'string',
177
+ description: 'Path to a JSON or YAML file with the full workflow body',
178
+ })
179
+ )
180
+ .example(
181
+ 'mint workflow create --name Translations --cron "0 6 * * *" --type translations',
182
+ ''
183
+ )
184
+ .example('mint workflow create --file workflow.yaml', ''),
185
+ async (argv) => {
186
+ const format = resolveFormat(argv);
187
+ try {
188
+ const subdomain = requireSubdomain(argv.subdomain);
189
+ const body: WorkflowBody = argv.file
190
+ ? ((await readWorkflowFile(argv.file)) as WorkflowBody)
191
+ : buildBodyFromFlags(argv as CreateArgs);
192
+
193
+ if (format === 'table') addLog(<SpinnerLog message="Creating workflow..." />);
194
+ const created = await createWorkflow(subdomain, body);
195
+ if (format === 'table') removeLastLog();
196
+
197
+ void trackEvent('cli.workflow.created', {
198
+ subdomain,
199
+ type: created.type,
200
+ triggerKind: describeTrigger(created.trigger).split('(')[0],
201
+ });
202
+
203
+ if (format === 'json') {
204
+ output(format, JSON.stringify(created, null, 2));
205
+ } else {
206
+ addLog(<SuccessLog message={`Created workflow ${created._id}`} />);
207
+ output(format, renderWorkflow(created));
208
+ }
209
+ await terminate(0);
210
+ } catch (err) {
211
+ if (format === 'table') removeLastLog();
212
+ const message = err instanceof Error ? err.message : 'unknown error';
213
+ if (format === 'json') {
214
+ process.stderr.write(`Error: ${message}\n`);
215
+ } else {
216
+ addLog(<ErrorLog message={message} />);
217
+ }
218
+ await terminate(1);
219
+ }
220
+ }
221
+ )
222
+ .command(
223
+ 'list',
224
+ 'List workflows for the current deployment',
225
+ (yargs) => withFormat(withSubdomain(yargs)),
226
+ async (argv) => {
227
+ const format = resolveFormat(argv);
228
+ try {
229
+ const subdomain = requireSubdomain(argv.subdomain);
230
+ if (format === 'table') addLog(<SpinnerLog message="Fetching workflows..." />);
231
+ const workflows = await listWorkflows(subdomain);
232
+ if (format === 'table') removeLastLog();
233
+
234
+ if (format === 'json') {
235
+ output(format, JSON.stringify(workflows, null, 2));
236
+ } else {
237
+ output(format, renderList(workflows));
238
+ }
239
+ await terminate(0);
240
+ } catch (err) {
241
+ if (format === 'table') removeLastLog();
242
+ const message = err instanceof Error ? err.message : 'unknown error';
243
+ if (format === 'json') {
244
+ process.stderr.write(`Error: ${message}\n`);
245
+ } else {
246
+ addLog(<ErrorLog message={message} />);
247
+ }
248
+ await terminate(1);
249
+ }
250
+ }
251
+ )
252
+ .command(
253
+ 'delete <id>',
254
+ 'Delete a workflow by ID',
255
+ (yargs) =>
256
+ withFormat(withSubdomain(yargs)).positional('id', {
257
+ type: 'string',
258
+ demandOption: true,
259
+ description: 'Workflow schema ID',
260
+ }),
261
+ async (argv) => {
262
+ const format = resolveFormat(argv);
263
+ try {
264
+ const subdomain = requireSubdomain(argv.subdomain);
265
+ if (format === 'table') addLog(<SpinnerLog message="Deleting workflow..." />);
266
+ await deleteWorkflow(subdomain, argv.id);
267
+ if (format === 'table') removeLastLog();
268
+ if (format === 'json') {
269
+ output(format, JSON.stringify({ success: true, id: argv.id }, null, 2));
270
+ } else {
271
+ addLog(<SuccessLog message={`Deleted workflow ${argv.id}`} />);
272
+ }
273
+ await terminate(0);
274
+ } catch (err) {
275
+ if (format === 'table') removeLastLog();
276
+ const message = err instanceof Error ? err.message : 'unknown error';
277
+ if (format === 'json') {
278
+ process.stderr.write(`Error: ${message}\n`);
279
+ } else {
280
+ addLog(<ErrorLog message={message} />);
281
+ }
282
+ await terminate(1);
283
+ }
284
+ }
285
+ )
286
+ .demandCommand(1, 'specify a subcommand: create, list, or delete');