@bpmnkit/cli 0.0.36 → 0.1.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/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  [![ai-assisted](https://img.shields.io/badge/AI--assisted-claude-8b5cf6?style=flat-square)](https://github.com/bpmnkit/monorepo)
10
10
  [![experimental](https://img.shields.io/badge/status-experimental-f59e0b?style=flat-square)](https://github.com/bpmnkit/monorepo)
11
11
 
12
- [Website](https://bpmnkit.com) · [Documentation](https://docs.bpmnkit.com) · [GitHub](https://github.com/bpmnkit/monorepo) · [Changelog](https://github.com/bpmnkit/monorepo/blob/main/apps/cli/CHANGELOG.md)
12
+ [Website](https://bpmnkit.com) · [Documentation](https://bpmnkit.com/docs) · [GitHub](https://github.com/bpmnkit/monorepo) · [Changelog](https://github.com/bpmnkit/monorepo/blob/main/apps/cli/CHANGELOG.md)
13
13
  </div>
14
14
 
15
15
  ---
@@ -103,10 +103,12 @@ casen instances list --state active
103
103
  | [`@bpmnkit/plugins`](https://www.npmjs.com/package/@bpmnkit/plugins) | 22 composable canvas plugins |
104
104
  | [`@bpmnkit/api`](https://www.npmjs.com/package/@bpmnkit/api) | Camunda 8 REST API TypeScript client |
105
105
  | [`@bpmnkit/ascii`](https://www.npmjs.com/package/@bpmnkit/ascii) | Render BPMN diagrams as Unicode ASCII art |
106
+ | [`@bpmnkit/docspack`](https://www.npmjs.com/package/@bpmnkit/docspack) | BPMN Kit docs as an offline docspack package for AI agents |
106
107
  | [`@bpmnkit/ui`](https://www.npmjs.com/package/@bpmnkit/ui) | Shared design tokens and UI components |
107
108
  | [`@bpmnkit/profiles`](https://www.npmjs.com/package/@bpmnkit/profiles) | Shared auth, profile storage, and client factories for CLI & proxy |
108
109
  | [`@bpmnkit/operate`](https://www.npmjs.com/package/@bpmnkit/operate) | Monitoring & operations frontend for Camunda clusters |
109
110
  | [`@bpmnkit/connector-gen`](https://www.npmjs.com/package/@bpmnkit/connector-gen) | Generate connector templates from OpenAPI specs |
111
+ | [`@bpmnkit/connectors`](https://www.npmjs.com/package/@bpmnkit/connectors) | Camunda 8 OOTB connector catalog and deterministic template application |
110
112
  | [`@bpmnkit/proxy`](https://www.npmjs.com/package/@bpmnkit/proxy) | Local AI bridge and Camunda API proxy server |
111
113
  | [`@bpmnkit/patterns`](https://www.npmjs.com/package/@bpmnkit/patterns) | Domain process patterns for BPMNKit AIKit |
112
114
  | [`@bpmnkit/reebe-wasm`](https://www.npmjs.com/package/@bpmnkit/reebe-wasm) | WebAssembly BPMN engine for browser simulation |
@@ -1,5 +1,84 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { resolve } from "node:path";
3
+ import { getTemplate, listConnectors, searchConnectors } from "@bpmnkit/connectors";
4
+ const searchCmd = {
5
+ name: "search",
6
+ description: "Search the bundled Camunda 8 out-of-the-box connector catalog (Slack, HTTP, AWS, AI Agent, …)",
7
+ args: [
8
+ { name: "query", description: 'Search terms, e.g. "slack" or "send email"', required: true },
9
+ ],
10
+ examples: [{ description: "Find the Slack connector", command: "casen connector search slack" }],
11
+ async run(ctx) {
12
+ const query = ctx.positional.join(" ");
13
+ const results = searchConnectors(query);
14
+ if (results.length === 0) {
15
+ ctx.output.info(`No connectors matched "${query}". Try 'casen connector list' to browse all.`);
16
+ return;
17
+ }
18
+ ctx.output.printList({ items: results }, [
19
+ { key: "id", header: "TEMPLATE ID", maxWidth: 48 },
20
+ { key: "direction", header: "DIRECTION", maxWidth: 18 },
21
+ { key: "taskType", header: "TASK TYPE", maxWidth: 32 },
22
+ { key: "description", header: "DESCRIPTION", maxWidth: 50 },
23
+ ]);
24
+ },
25
+ };
26
+ const listCatalogCmd = {
27
+ name: "list",
28
+ description: "List all bundled Camunda 8 out-of-the-box connector templates",
29
+ async run(ctx) {
30
+ ctx.output.printList({ items: listConnectors() }, [
31
+ { key: "id", header: "TEMPLATE ID", maxWidth: 48 },
32
+ { key: "direction", header: "DIRECTION", maxWidth: 18 },
33
+ { key: "taskType", header: "TASK TYPE", maxWidth: 32 },
34
+ ]);
35
+ },
36
+ };
37
+ const showCmd = {
38
+ name: "show",
39
+ description: "Show a connector template's required/optional input keys",
40
+ args: [
41
+ {
42
+ name: "templateId",
43
+ description: "Template id, e.g. io.camunda.connectors.Slack.v1",
44
+ required: true,
45
+ },
46
+ ],
47
+ examples: [
48
+ {
49
+ description: "Show the Slack connector's inputs",
50
+ command: "casen connector show io.camunda.connectors.Slack.v1",
51
+ },
52
+ ],
53
+ async run(ctx) {
54
+ const templateId = ctx.positional[0];
55
+ if (!templateId)
56
+ throw new Error("Missing required argument: <templateId>");
57
+ const summary = searchConnectors(templateId).find((s) => s.id === templateId) ??
58
+ listConnectors().find((s) => s.id === templateId);
59
+ if (!summary || !getTemplate(templateId)) {
60
+ throw new Error(`Unknown connector template "${templateId}". Use 'casen connector search <query>' to find one.`);
61
+ }
62
+ ctx.output.info(`${summary.name} (${summary.id})`);
63
+ if (summary.taskType)
64
+ ctx.output.info(`Task type: ${summary.taskType}`);
65
+ ctx.output.info(`Direction: ${summary.direction}`);
66
+ if (summary.description)
67
+ ctx.output.info(summary.description);
68
+ if (summary.requiredInputs.length > 0) {
69
+ ctx.output.info("\nRequired inputs:");
70
+ for (const i of summary.requiredInputs) {
71
+ ctx.output.info(` ${i.key}${i.isSecret ? " (secret)" : ""} — ${i.label}`);
72
+ }
73
+ }
74
+ if (summary.optionalInputs.length > 0) {
75
+ ctx.output.info("\nOptional inputs:");
76
+ for (const i of summary.optionalInputs) {
77
+ ctx.output.info(` ${i.key}${i.isSecret ? " (secret)" : ""} — ${i.label}`);
78
+ }
79
+ }
80
+ },
81
+ };
3
82
  export const connectorGroup = {
4
83
  name: "connector",
5
84
  description: "Generate Camunda REST connector element templates from OpenAPI specs",
@@ -167,6 +246,9 @@ export const connectorGroup = {
167
246
  ]);
168
247
  },
169
248
  },
249
+ searchCmd,
250
+ listCatalogCmd,
251
+ showCmd,
170
252
  ],
171
253
  };
172
254
  //# sourceMappingURL=connector.js.map
@@ -0,0 +1,70 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { basename, resolve } from "node:path";
3
+ import { getActiveProfile, getAuthHeader } from "@bpmnkit/profiles";
4
+ const ZEEBE_ADDRESS = (process.env.ZEEBE_ADDRESS ?? "http://localhost:26500").replace(/\/$/, "");
5
+ const deployCmd = {
6
+ name: "deploy",
7
+ description: "Deploy a BPMN (or DMN/form) file to local Reebe or the active Camunda 8 profile",
8
+ args: [{ name: "file", description: "Path to the resource file", required: true }],
9
+ flags: [
10
+ {
11
+ name: "target",
12
+ description: 'Deployment target: "local" (Reebe, via ZEEBE_ADDRESS) or "camunda8" (active profile)',
13
+ type: "string",
14
+ default: "local",
15
+ enum: ["local", "camunda8"],
16
+ },
17
+ ],
18
+ examples: [
19
+ { description: "Deploy to local Reebe", command: "casen deploy deploy order-process.bpmn" },
20
+ {
21
+ description: "Deploy to Camunda 8",
22
+ command: "casen deploy deploy order-process.bpmn --target camunda8",
23
+ },
24
+ ],
25
+ async run(ctx) {
26
+ const filePath = ctx.positional[0];
27
+ if (!filePath)
28
+ throw new Error("Missing required argument: <file>");
29
+ const absPath = resolve(filePath);
30
+ const content = await readFile(absPath, "utf-8").catch(() => {
31
+ throw new Error(`Cannot read file: ${absPath}`);
32
+ });
33
+ const formData = new FormData();
34
+ formData.append("resources[]", new Blob([content], { type: "application/octet-stream" }), basename(absPath));
35
+ const target = typeof ctx.flags.target === "string" ? ctx.flags.target : "local";
36
+ if (target === "camunda8") {
37
+ const profile = getActiveProfile();
38
+ if (!profile?.config.baseUrl) {
39
+ throw new Error("No active Camunda 8 profile. Run: casen profile create");
40
+ }
41
+ const authHeader = await getAuthHeader(profile.config);
42
+ const baseUrl = profile.config.baseUrl.replace(/\/$/, "");
43
+ const res = await fetch(`${baseUrl}/v2/deployments`, {
44
+ method: "POST",
45
+ headers: { authorization: authHeader },
46
+ body: formData,
47
+ });
48
+ if (!res.ok)
49
+ throw new Error(`Camunda 8 deploy failed: ${res.status} ${await res.text()}`);
50
+ ctx.output.print({ success: true, target: "camunda8", result: await res.json() });
51
+ return;
52
+ }
53
+ let res;
54
+ try {
55
+ res = await fetch(`${ZEEBE_ADDRESS}/v2/deployments`, { method: "POST", body: formData });
56
+ }
57
+ catch (err) {
58
+ throw new Error(`Cannot reach Reebe at ${ZEEBE_ADDRESS}. Start it with: casen reebe start\n${String(err)}`);
59
+ }
60
+ if (!res.ok)
61
+ throw new Error(`Local deploy failed: ${res.status} ${await res.text()}`);
62
+ ctx.output.print({ success: true, target: "local", result: await res.json() });
63
+ },
64
+ };
65
+ export const deployGroup = {
66
+ name: "deploy",
67
+ description: "Deploy BPMN/DMN/form resources to local Reebe or Camunda 8",
68
+ commands: [deployCmd],
69
+ };
70
+ //# sourceMappingURL=deploy.js.map
@@ -1,5 +1,6 @@
1
1
  import { readFile, writeFile } from "node:fs/promises";
2
- import { Bpmn, compactify, expand } from "@bpmnkit/core";
2
+ import { resolve } from "node:path";
3
+ import { Bpmn, applyAutoLayout, applyBpmnOperations, compactify, expand } from "@bpmnkit/core";
3
4
  // ── JSON schema reference ─────────────────────────────────────────────────────
4
5
  const SCHEMA_HELP = `CompactDiagram JSON schema — for --definition and stdin input
5
6
  ==============================================================
@@ -455,6 +456,35 @@ async function readStdin() {
455
456
  }
456
457
  return Buffer.concat(chunks).toString("utf-8").trim();
457
458
  }
459
+ // ── Output path guard ─────────────────────────────────────────────────────────
460
+ /**
461
+ * Decide where `--input` mode writes, refusing to replace the input file unless
462
+ * the caller asked for it explicitly.
463
+ *
464
+ * The patch itself is applied to the full model, so nothing outside it is lost.
465
+ * Replacing the source is still destructive — the diagram is re-laid out, and a
466
+ * mistaken patch has nowhere to be compared against — so in-place replacement
467
+ * has to be asked for by name.
468
+ *
469
+ * @param inputFile - The `--input` path.
470
+ * @param outputFlag - The `--output` path, if any. `-` (stdout) is handled by the caller.
471
+ * @param force - Whether `--force` was passed.
472
+ * @returns The path to write to.
473
+ * @throws If the write would replace `inputFile` and `force` is false.
474
+ */
475
+ export function resolveModifyOutputPath({ inputFile, outputFlag, force, }) {
476
+ const output = typeof outputFlag === "string" && outputFlag.length > 0 ? outputFlag : undefined;
477
+ const inPlace = output === undefined || resolve(output) === resolve(inputFile);
478
+ if (inPlace && !force) {
479
+ throw new Error([
480
+ `Refusing to overwrite ${inputFile}.`,
481
+ "The patch applies to the full model, so nothing is dropped, but the diagram is " +
482
+ "re-laid out and the original is gone once it is replaced.",
483
+ `Write elsewhere with --output <file>, or pass --force to replace ${inputFile} anyway.`,
484
+ ].join("\n"));
485
+ }
486
+ return output ?? inputFile;
487
+ }
458
488
  // ── Command ───────────────────────────────────────────────────────────────────
459
489
  const generateBpmnCmd = {
460
490
  name: "bpmn",
@@ -500,7 +530,9 @@ const generateBpmnCmd = {
500
530
  {
501
531
  name: "input",
502
532
  short: "f",
503
- description: "Existing .bpmn file to load and modify",
533
+ description: "Existing .bpmn file to load and modify. The patch is applied to the full model, so " +
534
+ "pools, lanes, data wiring and Zeebe detail are preserved; the diagram is re-laid " +
535
+ "out. Requires --output, or --force to replace it in place.",
504
536
  type: "string",
505
537
  },
506
538
  {
@@ -513,6 +545,11 @@ const generateBpmnCmd = {
513
545
  description: "Print the CompactDiagram JSON of --input and exit (for AI inspection of existing files)",
514
546
  type: "boolean",
515
547
  },
548
+ {
549
+ name: "force",
550
+ description: "Allow --input to be replaced in place. Prefer --output <file>.",
551
+ type: "boolean",
552
+ },
516
553
  ],
517
554
  examples: [
518
555
  {
@@ -553,11 +590,11 @@ const generateBpmnCmd = {
553
590
  },
554
591
  {
555
592
  description: "Add a new gateway path to an existing file",
556
- command: 'casen generate bpmn --input order.bpmn --patch \'{"elements":[{"id":"notify","type":"serviceTask","name":"Notify","jobType":"notify-worker"},{"id":"end2","type":"endEvent","name":"Notified"}],"flows":[{"id":"fn1","from":"gw","to":"notify","condition":"= urgent"},{"id":"fn2","from":"notify","to":"end2"}]}\'',
593
+ command: 'casen generate bpmn --input order.bpmn --output order.patched.bpmn --patch \'{"elements":[{"id":"notify","type":"serviceTask","name":"Notify","jobType":"notify-worker"},{"id":"end2","type":"endEvent","name":"Notified"}],"flows":[{"id":"fn1","from":"gw","to":"notify","condition":"= urgent"},{"id":"fn2","from":"notify","to":"end2"}]}\'',
557
594
  },
558
595
  {
559
596
  description: "Pipe a patch from AI output",
560
- command: 'echo \'{"elements":[...],"flows":[...]}\' | casen generate bpmn --input order.bpmn',
597
+ command: 'echo \'{"elements":[...],"flows":[...]}\' | casen generate bpmn --input order.bpmn --output order.patched.bpmn',
561
598
  },
562
599
  {
563
600
  description: "Re-apply auto-layout to an existing file",
@@ -580,12 +617,20 @@ const generateBpmnCmd = {
580
617
  if (inputFile) {
581
618
  const xml = await readFile(inputFile, "utf-8");
582
619
  const defs = Bpmn.parse(xml);
583
- const compact = compactify(defs);
584
620
  // --dump-compact: print JSON for AI inspection and exit
585
621
  if (ctx.flags["dump-compact"]) {
586
- process.stdout.write(`${JSON.stringify(compact, null, 2)}\n`);
622
+ process.stdout.write(`${JSON.stringify(compactify(defs), null, 2)}\n`);
587
623
  return;
588
624
  }
625
+ // Settle the destination before reading stdin or building the patch, so an
626
+ // unwritable target fails immediately instead of after the work is done.
627
+ const outputPath = outputFlag === "-"
628
+ ? null
629
+ : resolveModifyOutputPath({
630
+ inputFile,
631
+ outputFlag,
632
+ force: ctx.flags.force === true,
633
+ });
589
634
  // Resolve patch from --patch flag or stdin
590
635
  let patch = null;
591
636
  if (patchFlag) {
@@ -607,22 +652,40 @@ const generateBpmnCmd = {
607
652
  }
608
653
  }
609
654
  }
610
- // Apply patch to first process (covers all single-process cases)
655
+ // Apply the patch to the full model, not to the compact view of it: an
656
+ // element added this way leaves the document's pools, lanes, data
657
+ // wiring and Zeebe detail exactly where they were.
658
+ let edited = defs;
611
659
  if (patch) {
612
- const proc = compact.processes[0];
613
- if (!proc)
660
+ const process = defs.processes[0];
661
+ if (!process)
614
662
  throw new Error("Input BPMN has no processes");
615
- if (patch.elements?.length)
616
- proc.elements.push(...patch.elements);
617
- if (patch.flows?.length)
618
- proc.flows.push(...patch.flows);
663
+ const operations = [
664
+ ...(patch.elements ?? []).map((element) => ({ op: "insert", element, parent: process.id })),
665
+ ...(patch.flows ?? []).map((flow) => ({
666
+ op: "add_flow",
667
+ id: flow.id,
668
+ parent: process.id,
669
+ from: flow.from,
670
+ to: flow.to,
671
+ name: flow.name,
672
+ condition: flow.condition,
673
+ })),
674
+ ];
675
+ try {
676
+ edited = applyBpmnOperations(defs, operations).definitions;
677
+ }
678
+ catch (error) {
679
+ // Unresolved ids used to be skipped in silence, so a patch naming a
680
+ // misspelled element reported success and changed nothing.
681
+ throw new Error(`Patch could not be applied: ${error instanceof Error ? error.message : String(error)}`);
682
+ }
619
683
  }
620
- const patched = Bpmn.export(expand(compact));
621
- if (outputFlag === "-") {
684
+ const patched = Bpmn.export(applyAutoLayout(edited));
685
+ if (outputPath === null) {
622
686
  process.stdout.write(patched);
623
687
  return;
624
688
  }
625
- const outputPath = typeof outputFlag === "string" && outputFlag.length > 0 ? outputFlag : inputFile;
626
689
  await writeFile(outputPath, patched, "utf-8");
627
690
  ctx.output.ok(patch ? `Patched and written to ${outputPath}` : `Re-laid-out and written to ${outputPath}`);
628
691
  return;
@@ -4,8 +4,11 @@ import { askGroup } from "./ask.js";
4
4
  import { getDmnReqsXmlCmd, getDmnXmlCmd, getStartFormCmd, getUserTaskFormCmd, getXmlCmd, renderBpmnCmd, } from "./bpmn.js";
5
5
  import { completionGroup } from "./completion.js";
6
6
  import { connectorGroup } from "./connector.js";
7
+ import { deployGroup } from "./deploy.js";
7
8
  import { generateGroup } from "./generate.js";
8
9
  import { lintGroup } from "./lint.js";
10
+ import { patternGroup } from "./pattern.js";
11
+ import { planGroup } from "./plan.js";
9
12
  import { pluginGroup } from "./plugin.js";
10
13
  import { profileGroup } from "./profile.js";
11
14
  import { proxyGroup } from "./proxy.js";
@@ -14,6 +17,7 @@ import { computeRelations } from "./relations.js";
14
17
  import { settingsGroup } from "./settings.js";
15
18
  import { skillsGroup } from "./skills.js";
16
19
  import { storyGroup } from "./story.js";
20
+ import { synthGroup } from "./synth.js";
17
21
  import { testGroup } from "./test.js";
18
22
  import { viewGroup } from "./view.js";
19
23
  import { workerStartCmd } from "./worker-start.js";
@@ -57,13 +61,17 @@ const workerGroup = {
57
61
  /** Pinned groups shown above the separator in the main TUI menu. */
58
62
  export const pinnedGroups = [
59
63
  askGroup,
64
+ deployGroup,
60
65
  generateGroup,
61
66
  lintGroup,
67
+ patternGroup,
68
+ planGroup,
62
69
  proxyGroup,
63
70
  reebeGroup,
64
71
  skillsGroup,
65
72
  storyGroup,
66
73
  settingsGroup,
74
+ synthGroup,
67
75
  testGroup,
68
76
  viewGroup,
69
77
  workerGroup,
@@ -1,5 +1,17 @@
1
1
  import { readFile, writeFile } from "node:fs/promises";
2
+ import { applyConnectorTemplate } from "@bpmnkit/connectors";
2
3
  import { Bpmn, compactify, optimize } from "@bpmnkit/core";
4
+ /** Resolves a connector template's missing required keys via @bpmnkit/connectors, for the `connector/*` lint rule. */
5
+ export function resolveConnectorRequirements(templateId, boundKeys) {
6
+ const values = Object.fromEntries(boundKeys.map((k) => [k, "x"]));
7
+ const result = applyConnectorTemplate(templateId, values);
8
+ // Only "missing-required" problems mean something is actually unset — an "unknown-key"
9
+ // problem here just means a bound key doesn't match propertyKey()'s lookup key (e.g. a
10
+ // property whose `id` differs from its zeebe:input binding name), not a missing value.
11
+ return result.problems
12
+ .filter((p) => p.kind === "missing-required" && p.key !== undefined)
13
+ .map((p) => p.key);
14
+ }
3
15
  const SEVERITY_SYMBOL = {
4
16
  error: "✖",
5
17
  warning: "⚠",
@@ -16,6 +28,11 @@ const lintCmd = {
16
28
  description: "Comma-separated categories to run (default: all)",
17
29
  type: "string",
18
30
  },
31
+ {
32
+ name: "profile",
33
+ description: 'Lint profile. "deploy" runs every category and shows only error-severity findings — the deploy-readiness gate.',
34
+ type: "string",
35
+ },
19
36
  {
20
37
  name: "format",
21
38
  description: "Output format: text (default) or json",
@@ -27,6 +44,13 @@ const lintCmd = {
27
44
  type: "boolean",
28
45
  },
29
46
  ],
47
+ examples: [
48
+ { description: "Lint a file", command: "casen lint lint order-process.bpmn" },
49
+ {
50
+ description: "Deploy-readiness gate (errors only)",
51
+ command: "casen lint lint order-process.bpmn --profile deploy",
52
+ },
53
+ ],
30
54
  async run(ctx) {
31
55
  const filePath = ctx.positional[0];
32
56
  if (!filePath)
@@ -37,8 +61,14 @@ const lintCmd = {
37
61
  const categories = typeof categoriesFlag === "string" && categoriesFlag.length > 0
38
62
  ? categoriesFlag.split(",").map((s) => s.trim())
39
63
  : undefined;
40
- const report = optimize(defs, categories !== undefined ? { categories } : undefined);
41
- const { findings } = report;
64
+ const deployProfile = ctx.flags.profile === "deploy";
65
+ const report = optimize(defs, {
66
+ ...(categories !== undefined ? { categories } : {}),
67
+ resolveConnectorRequirements,
68
+ });
69
+ const findings = deployProfile
70
+ ? report.findings.filter((f) => f.severity === "error")
71
+ : report.findings;
42
72
  // --fix: apply all auto-fixable findings and write back
43
73
  if (ctx.flags.fix) {
44
74
  const fixable = findings.filter((f) => f.applyFix);
@@ -67,10 +97,10 @@ const lintCmd = {
67
97
  const elIds = f.elementIds.length > 0 ? ` [${f.elementIds.join(", ")}]` : "";
68
98
  ctx.output.info(`${symbol} [${f.category}]${elIds} ${f.message}`);
69
99
  }
70
- const { total, bySeverity } = report.summary;
71
- const errorCount = bySeverity.error ?? 0;
72
- const warnCount = bySeverity.warning ?? 0;
73
- const infoCount = bySeverity.info ?? 0;
100
+ const total = findings.length;
101
+ const errorCount = findings.filter((f) => f.severity === "error").length;
102
+ const warnCount = findings.filter((f) => f.severity === "warning").length;
103
+ const infoCount = findings.filter((f) => f.severity === "info").length;
74
104
  ctx.output.info(`\n${total} finding${total !== 1 ? "s" : ""}: ${errorCount} error${errorCount !== 1 ? "s" : ""}, ${warnCount} warning${warnCount !== 1 ? "s" : ""}, ${infoCount} info`);
75
105
  if (errorCount > 0) {
76
106
  throw new Error(`Lint failed with ${errorCount} error${errorCount !== 1 ? "s" : ""}`);
@@ -0,0 +1,55 @@
1
+ import { ALL_PATTERNS, findPattern } from "@bpmnkit/patterns";
2
+ const listCmd = {
3
+ name: "list",
4
+ description: "List the built-in domain process patterns",
5
+ examples: [{ description: "List all patterns", command: "casen pattern list" }],
6
+ async run(ctx) {
7
+ ctx.output.print(ALL_PATTERNS.map((p) => ({
8
+ id: p.id,
9
+ name: p.name,
10
+ description: p.description,
11
+ keywords: p.keywords,
12
+ })));
13
+ },
14
+ };
15
+ const getCmd = {
16
+ name: "get",
17
+ description: "Show a domain pattern's full context: readme, worker specs, and variations",
18
+ args: [
19
+ {
20
+ name: "query",
21
+ description: "Pattern id (e.g. invoice-approval) or a free-text description to match",
22
+ required: true,
23
+ },
24
+ ],
25
+ examples: [
26
+ { description: "By id", command: "casen pattern get invoice-approval" },
27
+ { description: "By description", command: 'casen pattern get "employee onboarding"' },
28
+ ],
29
+ async run(ctx) {
30
+ const query = ctx.positional.join(" ");
31
+ if (!query)
32
+ throw new Error("Missing required argument: <query>");
33
+ const pattern = findPattern(query);
34
+ if (!pattern) {
35
+ ctx.output.info(`No pattern matched "${query}". Run \`casen pattern list\` to see all patterns.`);
36
+ return;
37
+ }
38
+ ctx.output.print({
39
+ id: pattern.id,
40
+ name: pattern.name,
41
+ description: pattern.description,
42
+ readme: pattern.readme,
43
+ workers: pattern.workers,
44
+ variations: pattern.variations,
45
+ note: "`template` is a rough structural reference in an older compact-diagram shape, not a ProcessPlan — use the readme/workers as context when writing your own plan, don't paste the template in as-is.",
46
+ template: pattern.template,
47
+ });
48
+ },
49
+ };
50
+ export const patternGroup = {
51
+ name: "pattern",
52
+ description: "Look up built-in domain process patterns for AI-driven process generation",
53
+ commands: [listCmd, getCmd],
54
+ };
55
+ //# sourceMappingURL=pattern.js.map
@@ -0,0 +1,70 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { Bpmn, extractPlan } from "@bpmnkit/core";
4
+ const extractCmd = {
5
+ name: "extract",
6
+ description: "Lift an existing .bpmn file back into ProcessPlan JSON form",
7
+ args: [{ name: "file", description: "Path to the .bpmn file", required: true }],
8
+ flags: [
9
+ {
10
+ name: "output",
11
+ short: "o",
12
+ description: "Output .json file path (default: <file>.plan.json)",
13
+ type: "string",
14
+ },
15
+ ],
16
+ examples: [{ description: "Extract a plan", command: "casen plan extract order-process.bpmn" }],
17
+ async run(ctx) {
18
+ const filePath = ctx.positional[0];
19
+ if (!filePath)
20
+ throw new Error("Missing required argument: <file>");
21
+ const xml = await readFile(resolve(filePath), "utf-8");
22
+ const defs = Bpmn.parse(xml);
23
+ const { plan, unsupported } = extractPlan(defs);
24
+ const outputPath = resolve(typeof ctx.flags.output === "string"
25
+ ? ctx.flags.output
26
+ : `${filePath.replace(/\.bpmn$/, "")}.plan.json`);
27
+ await writeFile(outputPath, `${JSON.stringify(plan, null, "\t")}\n`, "utf-8");
28
+ ctx.output.ok(`Wrote ${outputPath}`);
29
+ if (unsupported.length > 0) {
30
+ ctx.output.info(`\n${unsupported.length} element(s) could not be lifted — left out of the plan, not guessed at:`);
31
+ for (const u of unsupported)
32
+ ctx.output.info(` [${u.id}] (${u.type}) ${u.reason}`);
33
+ }
34
+ },
35
+ };
36
+ export const PLAN_SCHEMA_SUMMARY = `ProcessPlan (version 1)
37
+ {
38
+ "version": 1,
39
+ "process": { "id": string, "name"?: string, "versionTag"?: string },
40
+ "inputs"?: [{ "name": string, "type": string, "required"?: boolean, "description"?: string }],
41
+ "steps": PlanStep[],
42
+ "tests"?: PlanScenario[]
43
+ }
44
+
45
+ PlanStep.kind: "start" | "connector" | "serviceTask" | "userTask" | "businessRuleTask" |
46
+ "scriptTask" | "sendTask" | "receiveTask" | "callActivity" | "aiAgent" | "gateway" |
47
+ "subProcess" | "wait" | "end" | "raw"
48
+
49
+ Every step: { id?, name?, documentation?, errorBoundary?: { errorCode, steps }, timerBoundary?: {...} }
50
+ FEEL convention: a leading "=" makes a string a FEEL expression; without it, the value is literal.
51
+
52
+ steps[0] must be kind "start". "gateway" steps carry { gatewayType, branches: [{ condition?, default?, steps }] }.
53
+ "connector"/"aiAgent" tool steps reference a bundled Camunda template via { template, values } —
54
+ see \`casen connector search <query>\` / \`casen connector show <templateId>\` for available templates
55
+ and their required/optional input keys.
56
+
57
+ Full type definitions: packages/core/src/plan/types.ts in the bpmnkit monorepo.`;
58
+ const schemaCmd = {
59
+ name: "schema",
60
+ description: "Print the ProcessPlan format reference",
61
+ async run(ctx) {
62
+ ctx.output.info(PLAN_SCHEMA_SUMMARY);
63
+ },
64
+ };
65
+ export const planGroup = {
66
+ name: "plan",
67
+ description: "ProcessPlan authoring — extract an existing process, or print the plan format reference",
68
+ commands: [extractCmd, schemaCmd],
69
+ };
70
+ //# sourceMappingURL=plan.js.map
@@ -4,7 +4,8 @@ import { fileURLToPath } from "node:url";
4
4
  /**
5
5
  * Install BPMNKit AIKit skills into `.claude/commands/` in the current project.
6
6
  * Skills are markdown prompt files that Claude Code executes as slash commands:
7
- * /design, /implement, /review, /test, /deploy
7
+ * /implement, /review, /test, /deploy
8
+ * Lightweight, CLI-only versions of the full plugin's skill set — see aikit.md.
8
9
  *
9
10
  * Also installs aikit.md into .claude/ as a shared tool reference.
10
11
  */
@@ -84,7 +85,10 @@ const skillsInstallCmd = {
84
85
  ctx.output.info(` /${name}`);
85
86
  }
86
87
  ctx.output.info("");
87
- ctx.output.info("Make sure the BPMNKit AIKit MCP server is configured in .claude/mcp.json");
88
+ ctx.output.info("These commands drive `casen` directly — no MCP server or proxy needed. " +
89
+ "For the full skill set (implement/extend/agent/connect/review/test/deploy + generated " +
90
+ "reference docs), install the Claude Code plugin instead: " +
91
+ "/plugin marketplace add github:bpmnkit/monorepo && /plugin install bpmnkit");
88
92
  },
89
93
  };
90
94
  export const skillsGroup = {