@agentled/cli 0.1.6 → 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.
Files changed (53) hide show
  1. package/README.md +136 -0
  2. package/dist/builtin-tools-catalog.d.ts +37 -0
  3. package/dist/builtin-tools-catalog.js +96 -0
  4. package/dist/builtin-tools-catalog.js.map +1 -0
  5. package/dist/commands/auth.js +30 -0
  6. package/dist/commands/auth.js.map +1 -1
  7. package/dist/commands/examples.d.ts +15 -0
  8. package/dist/commands/examples.js +100 -0
  9. package/dist/commands/examples.js.map +1 -0
  10. package/dist/commands/scaffold.d.ts +14 -0
  11. package/dist/commands/scaffold.js +103 -0
  12. package/dist/commands/scaffold.js.map +1 -0
  13. package/dist/commands/schema.d.ts +10 -0
  14. package/dist/commands/schema.js +107 -0
  15. package/dist/commands/schema.js.map +1 -0
  16. package/dist/commands/skills.d.ts +9 -0
  17. package/dist/commands/skills.js +94 -0
  18. package/dist/commands/skills.js.map +1 -0
  19. package/dist/commands/tools.d.ts +10 -0
  20. package/dist/commands/tools.js +53 -0
  21. package/dist/commands/tools.js.map +1 -0
  22. package/dist/commands/workflows.js +227 -9
  23. package/dist/commands/workflows.js.map +1 -1
  24. package/dist/context-schema.d.ts +37 -0
  25. package/dist/context-schema.js +108 -0
  26. package/dist/context-schema.js.map +1 -0
  27. package/dist/index.js +8 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/utils/preflight.d.ts +25 -0
  30. package/dist/utils/preflight.js +293 -0
  31. package/dist/utils/preflight.js.map +1 -0
  32. package/dist/utils/skills.d.ts +49 -0
  33. package/dist/utils/skills.js +214 -0
  34. package/dist/utils/skills.js.map +1 -0
  35. package/package.json +4 -1
  36. package/patterns/v1/00-why-agentic-ops.md +107 -0
  37. package/patterns/v1/01-trigger-design.md +107 -0
  38. package/patterns/v1/02-dedup-gates.md +135 -0
  39. package/patterns/v1/03-credit-efficiency.md +130 -0
  40. package/patterns/v1/04-loop-patterns.md +147 -0
  41. package/patterns/v1/05-child-workflow-contracts.md +151 -0
  42. package/patterns/v1/06-conditional-routing.md +151 -0
  43. package/patterns/v1/07-error-handling.md +157 -0
  44. package/patterns/v1/08-composed-email-approval.md +130 -0
  45. package/patterns/v1/09-reports-and-knowledge-storage.md +166 -0
  46. package/scaffolds/README.md +62 -0
  47. package/scaffolds/ai-with-tools.json +49 -0
  48. package/scaffolds/email-polling-dedup.json +71 -0
  49. package/scaffolds/extract-threshold-alert.json +131 -0
  50. package/scaffolds/lead-scoring-kg.json +84 -0
  51. package/scaffolds/list-match-email.json +131 -0
  52. package/scaffolds/minimal.json +20 -0
  53. package/skills/agentled/SKILL.md +573 -0
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `agentled schema` — fetch the canonical PipelineStep field schema from the
3
+ * live API (`GET /api/external/workflows/step-schema`).
4
+ *
5
+ * Wraps the existing `get_step_schema` MCP tool so the CLI-driven agent has
6
+ * a no-MCP way to look up "what fields are valid on an appAction step?" or
7
+ * "what's the minimal JSON for an aiAction?" before writing a pipeline.
8
+ */
9
+ import { Command } from 'commander';
10
+ export declare function registerSchemaCommand(program: Command): void;
@@ -0,0 +1,107 @@
1
+ /* eslint-disable no-console */
2
+ import { AgentledClient } from '../client.js';
3
+ import { printOutput, printError } from '../utils/output.js';
4
+ import { BASE_FIELD_KEYS, CONTEXT_FIELD_TYPES, CONTEXT_FIELD_TYPE_ALIASES, } from '../context-schema.js';
5
+ function filterByStepType(schema, stepType) {
6
+ const groups = schema.groups
7
+ .map(g => ({
8
+ ...g,
9
+ fields: g.fields.filter(f => !f.stepTypes || f.stepTypes.includes(stepType)),
10
+ }))
11
+ .filter(g => g.fields.length > 0);
12
+ const fieldCount = groups.reduce((n, g) => n + g.fields.length, 0);
13
+ return { ...schema, groups, fieldCount };
14
+ }
15
+ function renderTable(schema) {
16
+ const lines = [];
17
+ lines.push(schema.description);
18
+ lines.push(`(${schema.fieldCount} fields in ${schema.groups.length} categories)`);
19
+ lines.push('');
20
+ for (const g of schema.groups) {
21
+ lines.push(`## ${g.category}`);
22
+ lines.push(` ${g.description}`);
23
+ for (const f of g.fields) {
24
+ const req = f.required ? ' *' : '';
25
+ const types = f.stepTypes ? ` [${f.stepTypes.join(', ')}]` : '';
26
+ lines.push(` - ${f.name}${req}: ${f.type}${types}`);
27
+ lines.push(` ${f.description}`);
28
+ if (f.example)
29
+ lines.push(` example: ${f.example}`);
30
+ }
31
+ lines.push('');
32
+ }
33
+ return lines.join('\n');
34
+ }
35
+ function buildContextSchema() {
36
+ return {
37
+ description: 'Valid field types for pipeline.context input pages (`executionInputConfig.fields[]`, ' +
38
+ '`inputPages[].configuration.fields[]`). Unknown `type` values fall back to a plain text ' +
39
+ 'input at render time but lose type-aware validation and pickers.',
40
+ scope: 'context',
41
+ baseKeys: [...BASE_FIELD_KEYS],
42
+ fieldTypes: CONTEXT_FIELD_TYPES,
43
+ aliases: { ...CONTEXT_FIELD_TYPE_ALIASES },
44
+ };
45
+ }
46
+ function renderContextTable(schema) {
47
+ const lines = [];
48
+ lines.push(schema.description);
49
+ lines.push(`(${schema.fieldTypes.length} valid field types)`);
50
+ lines.push('');
51
+ lines.push(`Base keys (every field): ${schema.baseKeys.join(', ')}`);
52
+ lines.push('');
53
+ lines.push('Field types:');
54
+ for (const t of schema.fieldTypes) {
55
+ const extras = t.extraKeys?.length ? ` [+ ${t.extraKeys.join(', ')}]` : '';
56
+ lines.push(` - ${t.value}${extras}`);
57
+ lines.push(` ${t.description}`);
58
+ if (t.example) {
59
+ lines.push(` example: ${JSON.stringify(t.example)}`);
60
+ }
61
+ }
62
+ const aliasEntries = Object.entries(schema.aliases);
63
+ if (aliasEntries.length > 0) {
64
+ lines.push('');
65
+ lines.push('Legacy aliases (accepted but prefer the canonical name):');
66
+ for (const [alias, canonical] of aliasEntries) {
67
+ lines.push(` - ${alias} → ${canonical}`);
68
+ }
69
+ }
70
+ return lines.join('\n');
71
+ }
72
+ export function registerSchemaCommand(program) {
73
+ program
74
+ .command('schema')
75
+ .description('Show the canonical PipelineStep field schema, or the context/input-page field schema with --context.')
76
+ .option('--step-type <type>', 'Filter fields applicable to this step type (trigger, appAction, aiAction, code, …)')
77
+ .option('--context', 'Show the context / input-page field schema (pipeline.context.*.fields[].type) instead of the step schema')
78
+ .option('--format <format>', 'Output format: json (default), table, minimal', 'json')
79
+ .action(async (opts) => {
80
+ try {
81
+ const format = opts.format;
82
+ if (opts.context) {
83
+ const ctx = buildContextSchema();
84
+ if (format === 'table' || format === 'minimal') {
85
+ console.log(renderContextTable(ctx));
86
+ }
87
+ else {
88
+ printOutput(ctx, format);
89
+ }
90
+ return;
91
+ }
92
+ const client = new AgentledClient();
93
+ const schema = await client.getStepSchema();
94
+ const filtered = opts.stepType ? filterByStepType(schema, opts.stepType) : schema;
95
+ if (format === 'table' || format === 'minimal') {
96
+ console.log(renderTable(filtered));
97
+ }
98
+ else {
99
+ printOutput(filtered, format);
100
+ }
101
+ }
102
+ catch (e) {
103
+ printError(e.message);
104
+ }
105
+ });
106
+ }
107
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/commands/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `agentled skills` — manage the Claude Code skill bundled with the CLI.
3
+ *
4
+ * Matches the old `npx @agentled/mcp-server --setup-skills` flow but surfaces
5
+ * it as a first-class CLI verb so users can refresh skills after the CLI
6
+ * itself is updated.
7
+ */
8
+ import { Command } from 'commander';
9
+ export declare function registerSkillsCommands(program: Command): void;
@@ -0,0 +1,94 @@
1
+ /* eslint-disable no-console */
2
+ import { describeSkillsInstall, getSkillsTargetDir, installSkills, resolveBundledSkillsDir, } from '../utils/skills.js';
3
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ function parseSkillVersion(skillMdPath) {
6
+ try {
7
+ const content = readFileSync(skillMdPath, 'utf-8');
8
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
9
+ if (!fmMatch)
10
+ return null;
11
+ const versionMatch = fmMatch[1].match(/^version:\s*(.+)$/m);
12
+ return versionMatch ? versionMatch[1].trim() : null;
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ export function registerSkillsCommands(program) {
19
+ const skills = program
20
+ .command('skills')
21
+ .description('Manage Claude Code skills bundled with the CLI');
22
+ skills
23
+ .command('install')
24
+ .description('Install the bundled Agentled skill into ~/.claude/skills (global by default)')
25
+ .option('--project', 'Install into the current project\'s .claude/skills/ instead of the user-global location')
26
+ .option('--force', 'Overwrite existing skill files even if they contain local edits')
27
+ .action((opts) => {
28
+ try {
29
+ const isGlobal = !opts.project;
30
+ const targetDir = getSkillsTargetDir(isGlobal);
31
+ const results = installSkills({ global: isGlobal, force: opts.force });
32
+ console.log(describeSkillsInstall(results, targetDir));
33
+ }
34
+ catch (e) {
35
+ console.error(`Error: ${e.message}`);
36
+ process.exit(1);
37
+ }
38
+ });
39
+ skills
40
+ .command('update')
41
+ .description('Refresh the installed skill to the version bundled with this CLI')
42
+ .option('--project', 'Update the project-level skill (default: user-global)')
43
+ .option('--force', 'Overwrite even if the installed skill has local edits')
44
+ .action((opts) => {
45
+ try {
46
+ const isGlobal = !opts.project;
47
+ const targetDir = getSkillsTargetDir(isGlobal);
48
+ const results = installSkills({ global: isGlobal, force: opts.force });
49
+ console.log(describeSkillsInstall(results, targetDir));
50
+ }
51
+ catch (e) {
52
+ console.error(`Error: ${e.message}`);
53
+ process.exit(1);
54
+ }
55
+ });
56
+ skills
57
+ .command('status')
58
+ .description('Show bundled vs installed skill versions')
59
+ .option('--project', 'Check the project-level skills (default: user-global)')
60
+ .action((opts) => {
61
+ const isGlobal = !opts.project;
62
+ const targetDir = getSkillsTargetDir(isGlobal);
63
+ const sourceDir = resolveBundledSkillsDir();
64
+ if (!existsSync(sourceDir)) {
65
+ console.log(`No bundled skills found (${sourceDir}). Reinstall @agentled/cli.`);
66
+ return;
67
+ }
68
+ const bundled = readdirSync(sourceDir, { withFileTypes: true })
69
+ .filter(d => d.isDirectory())
70
+ .map(d => d.name);
71
+ if (bundled.length === 0) {
72
+ console.log('No skills bundled with this CLI version.');
73
+ return;
74
+ }
75
+ for (const name of bundled) {
76
+ const bundledVersion = parseSkillVersion(join(sourceDir, name, 'SKILL.md')) ?? '?';
77
+ const installedSkillMd = join(targetDir, name, 'SKILL.md');
78
+ const installedVersion = existsSync(installedSkillMd)
79
+ ? (parseSkillVersion(installedSkillMd) ?? 'unknown')
80
+ : null;
81
+ if (installedVersion === null) {
82
+ console.log(` ${name}: bundled v${bundledVersion}, not installed`);
83
+ }
84
+ else if (installedVersion === bundledVersion) {
85
+ console.log(` ${name}: bundled v${bundledVersion}, installed v${installedVersion} (up to date)`);
86
+ }
87
+ else {
88
+ console.log(` ${name}: bundled v${bundledVersion}, installed v${installedVersion}`);
89
+ }
90
+ }
91
+ console.log(`\nSkills directory: ${targetDir}`);
92
+ });
93
+ }
94
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/commands/skills.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `agentled tools builtins` — list the builtin tools accepted by
3
+ * `aiActionWithTools` steps (`tools[].builtinType` values).
4
+ *
5
+ * The canonical source is the `Tool.builtinType` union in
6
+ * `shared/types/pipeline/PipelineTypes.ts`; the CLI ships a mirror at
7
+ * `src/builtin-tools-catalog.ts` (drift-tested).
8
+ */
9
+ import { Command } from 'commander';
10
+ export declare function registerToolsCommands(program: Command): void;
@@ -0,0 +1,53 @@
1
+ /* eslint-disable no-console */
2
+ import { BUILTIN_TOOLS_CATALOG } from '../builtin-tools-catalog.js';
3
+ import { printOutput } from '../utils/output.js';
4
+ function renderTable() {
5
+ const lines = [];
6
+ lines.push(`Builtin tools for \`aiActionWithTools\` steps (${BUILTIN_TOOLS_CATALOG.length} total):`);
7
+ lines.push('');
8
+ for (const t of BUILTIN_TOOLS_CATALOG) {
9
+ const providers = t.providers.join(', ');
10
+ const credits = t.typicalCreditsPerCall ? ` (~${t.typicalCreditsPerCall} credits/call)` : '';
11
+ lines.push(` - ${t.value} [providers: ${providers}]${credits}`);
12
+ lines.push(` ${t.description}`);
13
+ if (t.extraKeys?.length) {
14
+ lines.push(` extra keys: ${t.extraKeys.join(', ')}`);
15
+ }
16
+ lines.push(` example: ${JSON.stringify(t.example)}`);
17
+ }
18
+ lines.push('');
19
+ lines.push('Usage on a step:');
20
+ lines.push(' {');
21
+ lines.push(' "id": "analyze",');
22
+ lines.push(' "type": "aiActionWithTools",');
23
+ lines.push(' "name": "Analyze with tools",');
24
+ lines.push(' "tools": [');
25
+ lines.push(' { "type": "builtin", "name": "web_search", "builtinType": "web_search" },');
26
+ lines.push(' { "type": "builtin", "name": "workspace_memory", "builtinType": "workspace_memory" }');
27
+ lines.push(' ],');
28
+ lines.push(' "pipelineStepPrompt": { "template": "…", "responseStructure": { … } },');
29
+ lines.push(' "creditCost": 10,');
30
+ lines.push(' "next": { "stepId": "…" }');
31
+ lines.push(' }');
32
+ lines.push('');
33
+ lines.push('Scaffold: `agentled workflows scaffold ai-with-tools`');
34
+ return lines.join('\n');
35
+ }
36
+ export function registerToolsCommands(program) {
37
+ const tools = program
38
+ .command('tools')
39
+ .description('Discover runtime tools (e.g. builtin tools for aiActionWithTools).');
40
+ tools
41
+ .command('builtins')
42
+ .description('List builtin tools (`tools[].builtinType`) accepted by aiActionWithTools steps.')
43
+ .option('--format <format>', 'Output format: table (default), json', 'table')
44
+ .action((opts) => {
45
+ const format = opts.format;
46
+ if (format === 'json' || format === 'minimal') {
47
+ printOutput({ builtinTools: BUILTIN_TOOLS_CATALOG }, format);
48
+ return;
49
+ }
50
+ console.log(renderTable());
51
+ });
52
+ }
53
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/commands/tools.ts"],"names":[],"mappings":"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"}
@@ -1,11 +1,97 @@
1
+ /* eslint-disable no-console */
1
2
  import { AgentledClient } from '../client.js';
2
3
  import { printOutput, printError } from '../utils/output.js';
4
+ import { preflightPipeline, formatPreflightIssue } from '../utils/preflight.js';
5
+ import { registerScaffoldCommand } from './scaffold.js';
3
6
  import * as fs from 'node:fs';
7
+ /**
8
+ * Exit code used when a workflow is created/updated but server-side validation
9
+ * reports errors. Distinct from `1` (user/network error) so CI pipelines can
10
+ * detect "the pipeline JSON is broken" vs "the CLI itself failed".
11
+ */
12
+ const VALIDATION_EXIT_CODE = 2;
13
+ function formatIssueLine(issue) {
14
+ const scope = issue.stepId ? `[${issue.stepId}] ` : '';
15
+ const code = issue.code ? ` (${issue.code})` : '';
16
+ const msg = typeof issue.message === 'string' ? issue.message : JSON.stringify(issue);
17
+ const fix = issue.suggestedFix ? `\n fix: ${issue.suggestedFix}` : '';
18
+ return ` - ${scope}${msg}${code}${fix}`;
19
+ }
20
+ /**
21
+ * Calls validate_workflow on a freshly created/updated workflow. Returns a
22
+ * tagged outcome instead of a boolean so callers can distinguish "broken
23
+ * pipeline" (exit 2) from "CLI/network failure" (exit 1).
24
+ *
25
+ * In pretty formats this function prints the human-readable report directly.
26
+ * In JSON format it is silent — the caller is responsible for emitting the
27
+ * combined envelope so fields from the create/update mutation response
28
+ * (name, pathname, status, workspaceId, …) are preserved for script consumers.
29
+ *
30
+ * When called from `workflows create`, the caller sets `createdId` so the
31
+ * fix-flow hint can tell the user how to clean up.
32
+ */
33
+ async function runAutoValidate(client, workflowId, opts) {
34
+ let result;
35
+ try {
36
+ result = await client.validateWorkflow(workflowId);
37
+ }
38
+ catch (e) {
39
+ // The workflow was saved; only the post-hoc validate call failed.
40
+ // Surface as a structured warning but do not treat it as a pipeline
41
+ // defect — callers map this outcome to exit code 1, not 2.
42
+ console.error(`\nValidation request failed: ${e.message}`);
43
+ console.error(` The workflow was saved as ${workflowId}, but we could not validate it automatically.`);
44
+ console.error(` Run: agentled workflows validate ${workflowId}`);
45
+ return { status: 'request-failed' };
46
+ }
47
+ if (opts.format !== 'json') {
48
+ const errorCount = result.errors?.length ?? 0;
49
+ const warnCount = result.warnings?.length ?? 0;
50
+ if (errorCount === 0 && warnCount === 0) {
51
+ console.log(` ✓ Validated (0 errors, 0 warnings) — ${workflowId}`);
52
+ }
53
+ else {
54
+ if (errorCount > 0) {
55
+ console.error(`\n ✗ Validation failed: ${errorCount} error${errorCount === 1 ? '' : 's'}`);
56
+ for (const e of result.errors)
57
+ console.error(formatIssueLine(e));
58
+ }
59
+ if (warnCount > 0) {
60
+ console.log(`\n ⚠ ${warnCount} warning${warnCount === 1 ? '' : 's'}`);
61
+ for (const w of result.warnings)
62
+ console.log(formatIssueLine(w));
63
+ }
64
+ }
65
+ }
66
+ if (!result.valid) {
67
+ const id = opts.createdId ?? workflowId;
68
+ console.error(`\n Workflow ${id} was saved but is broken. Fix flow:`);
69
+ console.error(` 1. agentled workflows export ${id} --output pipeline.json`);
70
+ console.error(` 2. edit pipeline.json (use \`agentled workflows scaffold <pattern>\` or \`agentled examples <pattern>\` for reference)`);
71
+ console.error(` 3. agentled workflows validate --file pipeline.json # local preflight, no API call`);
72
+ console.error(` 4. agentled workflows update ${id} --file pipeline.json`);
73
+ console.error(` (or) agentled workflows delete ${id}`);
74
+ }
75
+ return { status: result.valid ? 'valid' : 'invalid', result };
76
+ }
77
+ /**
78
+ * Map a ValidateOutcome to its CLI exit code. `valid` returns 0 (caller does
79
+ * not exit), `invalid` returns 2 (pipeline broken), `request-failed` returns
80
+ * 1 (CLI/network problem).
81
+ */
82
+ function exitCodeForOutcome(outcome) {
83
+ if (outcome.status === 'valid')
84
+ return 0;
85
+ if (outcome.status === 'invalid')
86
+ return VALIDATION_EXIT_CODE;
87
+ return 1;
88
+ }
4
89
  export function registerWorkflowCommands(program) {
5
90
  const workflows = program
6
91
  .command('workflows')
7
92
  .alias('wf')
8
93
  .description('Manage workflows');
94
+ registerScaffoldCommand(workflows);
9
95
  workflows
10
96
  .command('list')
11
97
  .description('List all workflows in the workspace')
@@ -41,11 +127,12 @@ export function registerWorkflowCommands(program) {
41
127
  });
42
128
  workflows
43
129
  .command('create')
44
- .description('Create a new workflow from pipeline JSON')
130
+ .description('Create a new workflow from pipeline JSON (auto-validates unless --skip-validate)')
45
131
  .option('--file <path>', 'Path to pipeline JSON file')
46
132
  .option('--pipeline <json>', 'Inline pipeline JSON')
47
133
  .option('--locale <locale>', 'Locale (default: en)')
48
134
  .option('--format <fmt>', 'Output format', 'json')
135
+ .option('--skip-validate', 'Skip the post-create validate call (advanced — legacy raw create behavior)')
49
136
  .action(async (opts) => {
50
137
  try {
51
138
  let pipeline;
@@ -61,8 +148,40 @@ export function registerWorkflowCommands(program) {
61
148
  return;
62
149
  }
63
150
  const client = new AgentledClient();
151
+ const format = (opts.format ?? 'json');
64
152
  const result = await client.createWorkflow(pipeline, opts.locale);
65
- printOutput(result, opts.format);
153
+ const workflowId = (result?.id ?? result?.workflow?.id);
154
+ if (opts.skipValidate || !workflowId) {
155
+ printOutput(result, format);
156
+ return;
157
+ }
158
+ if (format === 'json') {
159
+ // Compound envelope: preserve every field from the create
160
+ // mutation response (name, pathname, status, workspaceId, …)
161
+ // AND include the validation report. Scripts that parse
162
+ // top-level fields continue to work.
163
+ const outcome = await runAutoValidate(client, workflowId, {
164
+ format,
165
+ createdId: workflowId,
166
+ });
167
+ const validation = outcome.status === 'request-failed'
168
+ ? { requestFailed: true }
169
+ : outcome.result;
170
+ printOutput({ ...result, validation }, 'json');
171
+ const code = exitCodeForOutcome(outcome);
172
+ if (code !== 0)
173
+ process.exit(code);
174
+ return;
175
+ }
176
+ // Pretty-print formats: emit the create response first, then validation.
177
+ printOutput(result, format);
178
+ const outcome = await runAutoValidate(client, workflowId, {
179
+ format,
180
+ createdId: workflowId,
181
+ });
182
+ const code = exitCodeForOutcome(outcome);
183
+ if (code !== 0)
184
+ process.exit(code);
66
185
  }
67
186
  catch (e) {
68
187
  printError(e.message);
@@ -70,11 +189,12 @@ export function registerWorkflowCommands(program) {
70
189
  });
71
190
  workflows
72
191
  .command('update <id>')
73
- .description('Update an existing workflow (partial update)')
192
+ .description('Update an existing workflow (auto-validates unless --skip-validate)')
74
193
  .option('--file <path>', 'Path to updates JSON file')
75
194
  .option('--updates <json>', 'Inline updates JSON')
76
195
  .option('--locale <locale>', 'Locale')
77
196
  .option('--format <fmt>', 'Output format', 'json')
197
+ .option('--skip-validate', 'Skip the post-update validate call (advanced)')
78
198
  .action(async (id, opts) => {
79
199
  try {
80
200
  let updates;
@@ -90,8 +210,34 @@ export function registerWorkflowCommands(program) {
90
210
  return;
91
211
  }
92
212
  const client = new AgentledClient();
213
+ const format = (opts.format ?? 'json');
93
214
  const result = await client.updateWorkflow(id, updates, opts.locale);
94
- printOutput(result, opts.format);
215
+ if (opts.skipValidate) {
216
+ printOutput(result, format);
217
+ return;
218
+ }
219
+ if (format === 'json') {
220
+ // Preserve the update mutation response fields in the JSON
221
+ // envelope — `result` may contain the refreshed workflow
222
+ // record, which script consumers parse.
223
+ const outcome = await runAutoValidate(client, id, { format });
224
+ const validation = outcome.status === 'request-failed'
225
+ ? { requestFailed: true }
226
+ : outcome.result;
227
+ const envelope = result && typeof result === 'object'
228
+ ? { ...result, validation }
229
+ : { workflowId: id, validation };
230
+ printOutput(envelope, 'json');
231
+ const code = exitCodeForOutcome(outcome);
232
+ if (code !== 0)
233
+ process.exit(code);
234
+ return;
235
+ }
236
+ printOutput(result, format);
237
+ const outcome = await runAutoValidate(client, id, { format });
238
+ const code = exitCodeForOutcome(outcome);
239
+ if (code !== 0)
240
+ process.exit(code);
95
241
  }
96
242
  catch (e) {
97
243
  printError(e.message);
@@ -112,16 +258,88 @@ export function registerWorkflowCommands(program) {
112
258
  }
113
259
  });
114
260
  workflows
115
- .command('validate <id>')
116
- .description('Validate workflow pipeline structure')
117
- .option('--pipeline <json>', 'Optional draft pipeline to validate before saving')
261
+ .command('validate [id]')
262
+ .description('Validate a workflow. With <id>: server-side validation. With --file/--pipeline and no id: fast client-side preflight (no API call).')
263
+ .option('--file <path>', 'Path to pipeline JSON file (client-side preflight if no <id> is given)')
264
+ .option('--pipeline <json>', 'Inline pipeline JSON (overrides the saved pipeline when <id> is given; otherwise runs preflight)')
118
265
  .option('--format <fmt>', 'Output format', 'json')
119
266
  .action(async (id, opts) => {
120
267
  try {
121
- const pipeline = opts.pipeline ? JSON.parse(opts.pipeline) : undefined;
268
+ let pipeline = undefined;
269
+ if (opts.file) {
270
+ pipeline = JSON.parse(fs.readFileSync(opts.file, 'utf-8'));
271
+ }
272
+ else if (opts.pipeline) {
273
+ pipeline = JSON.parse(opts.pipeline);
274
+ }
275
+ // No workflow id → run a local preflight. This is the "pre-create"
276
+ // path: catches invalid step types, duplicate ids, dangling next
277
+ // references, stripped root fields, missing prompt templates —
278
+ // without spending a create+validate+delete round trip.
279
+ if (!id) {
280
+ if (!pipeline) {
281
+ printError('Provide either <id> (server validate) or --file/--pipeline (client preflight).');
282
+ return;
283
+ }
284
+ const result = preflightPipeline(pipeline);
285
+ const format = opts.format;
286
+ if (format === 'json') {
287
+ printOutput({ preflight: true, ...result }, 'json');
288
+ }
289
+ else {
290
+ if (result.valid) {
291
+ console.log(` ✓ Preflight OK (${result.stepCount} steps, ${result.warnings.length} warnings)`);
292
+ }
293
+ else {
294
+ console.error(`\n ✗ Preflight failed: ${result.errors.length} error${result.errors.length === 1 ? '' : 's'}`);
295
+ for (const e of result.errors)
296
+ console.error(formatPreflightIssue(e));
297
+ }
298
+ if (result.warnings.length > 0) {
299
+ console.log(`\n ⚠ ${result.warnings.length} warning${result.warnings.length === 1 ? '' : 's'}`);
300
+ for (const w of result.warnings)
301
+ console.log(formatPreflightIssue(w));
302
+ }
303
+ console.log('\n Note: preflight is a fast structural check. Run `agentled workflows create` for full server-side validation.');
304
+ }
305
+ if (!result.valid)
306
+ process.exit(VALIDATION_EXIT_CODE);
307
+ return;
308
+ }
122
309
  const client = new AgentledClient();
123
310
  const result = await client.validateWorkflow(id, pipeline);
124
- printOutput(result, opts.format);
311
+ const format = opts.format;
312
+ if (format === 'json') {
313
+ printOutput(result, 'json');
314
+ }
315
+ else {
316
+ const errorCount = result.errors?.length ?? 0;
317
+ const warnCount = result.warnings?.length ?? 0;
318
+ if (errorCount === 0 && warnCount === 0) {
319
+ console.log(` ✓ Validated (0 errors, 0 warnings) — ${id}`);
320
+ }
321
+ else {
322
+ if (errorCount > 0) {
323
+ console.error(`\n ✗ Validation failed: ${errorCount} error${errorCount === 1 ? '' : 's'}`);
324
+ for (const e of result.errors)
325
+ console.error(formatIssueLine(e));
326
+ }
327
+ if (warnCount > 0) {
328
+ console.log(`\n ⚠ ${warnCount} warning${warnCount === 1 ? '' : 's'}`);
329
+ for (const w of result.warnings)
330
+ console.log(formatIssueLine(w));
331
+ }
332
+ }
333
+ }
334
+ if (!result.valid) {
335
+ console.error(`\n Fix flow:`);
336
+ console.error(` 1. agentled workflows export ${id} --output pipeline.json`);
337
+ console.error(` 2. edit pipeline.json`);
338
+ console.error(` 3. agentled workflows validate --file pipeline.json # local preflight`);
339
+ console.error(` 4. agentled workflows update ${id} --file pipeline.json`);
340
+ console.error(` (or) agentled workflows delete ${id}`);
341
+ process.exit(VALIDATION_EXIT_CODE);
342
+ }
125
343
  }
126
344
  catch (e) {
127
345
  printError(e.message);