create-byan-agent 2.12.1 → 2.13.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.
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Workflow edges — thin wrapper over byan_web REST endpoints.
3
+ *
4
+ * Wraps :
5
+ * POST /api/workflows/:workflowId/edges
6
+ * POST /api/workflows/:workflowId/edges/bulk
7
+ * GET /api/workflows/:workflowId/edges
8
+ * PATCH /api/workflow-edges/:edgeId
9
+ * DELETE /api/workflow-edges/:edgeId
10
+ *
11
+ * `apiRequest` is injected so tests can stub without real HTTP.
12
+ */
13
+
14
+ export const ALLOWED_EDGE_KINDS = ['always', 'success', 'error', 'expr'];
15
+
16
+ function encodeId(id) {
17
+ if (!id || typeof id !== 'string') throw new Error('id must be a non-empty string');
18
+ return encodeURIComponent(id);
19
+ }
20
+
21
+ function unwrap(body) {
22
+ if (body && typeof body === 'object' && 'data' in body) return body.data;
23
+ return body;
24
+ }
25
+
26
+ function validateEdgeShape({ from_node_id, to_node_id, condition_kind, condition_expr }) {
27
+ if (!from_node_id) throw new Error('from_node_id is required');
28
+ if (!to_node_id) throw new Error('to_node_id is required');
29
+ if (from_node_id === to_node_id) throw new Error('self-loop forbidden (from_node_id == to_node_id)');
30
+ if (condition_kind !== undefined && !ALLOWED_EDGE_KINDS.includes(condition_kind)) {
31
+ throw new Error(`condition_kind must be one of ${ALLOWED_EDGE_KINDS.join(', ')}, got ${condition_kind}`);
32
+ }
33
+ if (condition_kind === 'expr' && (!condition_expr || typeof condition_expr !== 'string' || condition_expr.trim().length === 0)) {
34
+ throw new Error('condition_expr required when condition_kind = "expr"');
35
+ }
36
+ }
37
+
38
+ export async function createEdge({
39
+ apiRequest, workflowId, from_node_id, to_node_id, condition_kind, condition_expr, label
40
+ }) {
41
+ if (!workflowId) throw new Error('workflowId is required');
42
+ validateEdgeShape({ from_node_id, to_node_id, condition_kind, condition_expr });
43
+
44
+ const payload = { from_node_id, to_node_id };
45
+ if (condition_kind !== undefined) payload.condition_kind = condition_kind;
46
+ if (condition_expr !== undefined) payload.condition_expr = condition_expr;
47
+ if (label !== undefined) payload.label = label;
48
+
49
+ const body = await apiRequest(`/api/workflows/${encodeId(workflowId)}/edges`, {
50
+ method: 'POST',
51
+ body: JSON.stringify(payload)
52
+ });
53
+ return unwrap(body);
54
+ }
55
+
56
+ export async function listEdges({ apiRequest, workflowId }) {
57
+ if (!workflowId) throw new Error('workflowId is required');
58
+ const body = await apiRequest(`/api/workflows/${encodeId(workflowId)}/edges`);
59
+ return { data: body.data || [], total: body.total ?? (body.data || []).length };
60
+ }
61
+
62
+ export async function updateEdge({
63
+ apiRequest, edgeId, label, condition_kind, condition_expr
64
+ }) {
65
+ const payload = {};
66
+ if (label !== undefined) payload.label = label;
67
+ if (condition_kind !== undefined) {
68
+ if (!ALLOWED_EDGE_KINDS.includes(condition_kind)) {
69
+ throw new Error(`condition_kind must be one of ${ALLOWED_EDGE_KINDS.join(', ')}, got ${condition_kind}`);
70
+ }
71
+ payload.condition_kind = condition_kind;
72
+ }
73
+ if (condition_expr !== undefined) payload.condition_expr = condition_expr;
74
+
75
+ if (Object.keys(payload).length === 0) {
76
+ throw new Error('At least one patchable field is required');
77
+ }
78
+
79
+ const body = await apiRequest(`/api/workflow-edges/${encodeId(edgeId)}`, {
80
+ method: 'PATCH',
81
+ body: JSON.stringify(payload)
82
+ });
83
+ return unwrap(body);
84
+ }
85
+
86
+ export async function deleteEdge({ apiRequest, edgeId }) {
87
+ const body = await apiRequest(`/api/workflow-edges/${encodeId(edgeId)}`, { method: 'DELETE' });
88
+ return unwrap(body);
89
+ }
90
+
91
+ export async function bulkCreateEdges({ apiRequest, workflowId, edges }) {
92
+ if (!workflowId) throw new Error('workflowId is required');
93
+ if (!Array.isArray(edges) || edges.length === 0) {
94
+ throw new Error('edges (non-empty array) is required');
95
+ }
96
+ for (let i = 0; i < edges.length; i++) {
97
+ try { validateEdgeShape(edges[i]); }
98
+ catch (err) { throw new Error(`edges[${i}]: ${err.message}`); }
99
+ }
100
+ const body = await apiRequest(`/api/workflows/${encodeId(workflowId)}/edges/bulk`, {
101
+ method: 'POST',
102
+ body: JSON.stringify({ edges })
103
+ });
104
+ return unwrap(body);
105
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Workflow nodes — thin wrapper over byan_web REST endpoints.
3
+ *
4
+ * Wraps :
5
+ * POST /api/workflows/:workflowId/nodes
6
+ * GET /api/workflows/:workflowId/nodes
7
+ * GET /api/workflow-nodes/:nodeId
8
+ * PATCH /api/workflow-nodes/:nodeId
9
+ * DELETE /api/workflow-nodes/:nodeId
10
+ *
11
+ * `apiRequest` is injected so tests can stub without real HTTP.
12
+ */
13
+
14
+ export const ALLOWED_NODE_TYPES = ['trigger', 'agent', 'worker', 'context', 'condition', 'action', 'output'];
15
+
16
+ function encodeId(id) {
17
+ if (!id || typeof id !== 'string') throw new Error('id must be a non-empty string');
18
+ return encodeURIComponent(id);
19
+ }
20
+
21
+ function unwrap(body) {
22
+ if (body && typeof body === 'object' && 'data' in body) return body.data;
23
+ return body;
24
+ }
25
+
26
+ export async function createNode({
27
+ apiRequest, workflowId, id, type, label, config, x, y, tier, llm_profile, tier_locked
28
+ }) {
29
+ if (!workflowId) throw new Error('workflowId is required');
30
+ if (!ALLOWED_NODE_TYPES.includes(type)) {
31
+ throw new Error(`type must be one of ${ALLOWED_NODE_TYPES.join(', ')}, got ${type}`);
32
+ }
33
+ const payload = { type };
34
+ if (id !== undefined) payload.id = id;
35
+ if (label !== undefined) payload.label = label;
36
+ if (config !== undefined) payload.config = config;
37
+ if (x !== undefined) payload.x = x;
38
+ if (y !== undefined) payload.y = y;
39
+ if (tier !== undefined) payload.tier = tier;
40
+ if (llm_profile !== undefined) payload.llm_profile = llm_profile;
41
+ if (tier_locked !== undefined) payload.tier_locked = tier_locked;
42
+
43
+ const body = await apiRequest(`/api/workflows/${encodeId(workflowId)}/nodes`, {
44
+ method: 'POST',
45
+ body: JSON.stringify(payload)
46
+ });
47
+ return unwrap(body);
48
+ }
49
+
50
+ export async function listNodes({ apiRequest, workflowId }) {
51
+ if (!workflowId) throw new Error('workflowId is required');
52
+ const body = await apiRequest(`/api/workflows/${encodeId(workflowId)}/nodes`);
53
+ return { data: body.data || [], total: body.total ?? (body.data || []).length };
54
+ }
55
+
56
+ export async function getNode({ apiRequest, nodeId }) {
57
+ const body = await apiRequest(`/api/workflow-nodes/${encodeId(nodeId)}`);
58
+ return unwrap(body);
59
+ }
60
+
61
+ export async function updateNode({
62
+ apiRequest, nodeId, type, label, config, x, y, tier, llm_profile, tier_locked
63
+ }) {
64
+ const payload = {};
65
+ if (type !== undefined) {
66
+ if (!ALLOWED_NODE_TYPES.includes(type)) {
67
+ throw new Error(`type must be one of ${ALLOWED_NODE_TYPES.join(', ')}, got ${type}`);
68
+ }
69
+ payload.type = type;
70
+ }
71
+ if (label !== undefined) payload.label = label;
72
+ if (config !== undefined) payload.config = config;
73
+ if (x !== undefined) payload.x = x;
74
+ if (y !== undefined) payload.y = y;
75
+ if (tier !== undefined) payload.tier = tier;
76
+ if (llm_profile !== undefined) payload.llm_profile = llm_profile;
77
+ if (tier_locked !== undefined) payload.tier_locked = tier_locked;
78
+
79
+ if (Object.keys(payload).length === 0) {
80
+ throw new Error('At least one patchable field is required');
81
+ }
82
+
83
+ const body = await apiRequest(`/api/workflow-nodes/${encodeId(nodeId)}`, {
84
+ method: 'PATCH',
85
+ body: JSON.stringify(payload)
86
+ });
87
+ return unwrap(body);
88
+ }
89
+
90
+ export async function deleteNode({ apiRequest, nodeId }) {
91
+ const body = await apiRequest(`/api/workflow-nodes/${encodeId(nodeId)}`, { method: 'DELETE' });
92
+ return unwrap(body);
93
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "byan-mcp-server",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "BYAN MCP server — exposes byan_web REST API (projects, FD lifecycle, kanban, ELO, fact-check, workflow-node-scripts, peer-review) as Claude Code tools.",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -55,6 +55,20 @@ import {
55
55
  ALLOWED_LANGUAGES as WF_SCRIPT_LANGUAGES,
56
56
  ALLOWED_EXEC_MODES as WF_SCRIPT_EXEC_MODES,
57
57
  } from './lib/workflow-scripts.js';
58
+ import {
59
+ createNode,
60
+ listNodes,
61
+ getNode,
62
+ updateNode,
63
+ deleteNode,
64
+ } from './lib/workflow-nodes.js';
65
+ import {
66
+ createEdge,
67
+ listEdges,
68
+ updateEdge,
69
+ deleteEdge,
70
+ bulkCreateEdges,
71
+ } from './lib/workflow-edges.js';
58
72
 
59
73
  const BYAN_API_URL = process.env.BYAN_API_URL || 'http://localhost:3737';
60
74
  const BYAN_API_TOKEN = process.env.BYAN_API_TOKEN || '';
@@ -688,6 +702,166 @@ const tools = [
688
702
  additionalProperties: false,
689
703
  },
690
704
  },
705
+ {
706
+ name: 'byan_workflow_node_create',
707
+ description:
708
+ 'Add a node to a workflow. Node types: trigger, agent, worker, context, condition, action, output. Provide id to use a human-readable key (e.g. "p0-preflight"); otherwise a UUID is generated.',
709
+ inputSchema: {
710
+ type: 'object',
711
+ properties: {
712
+ workflowId: { type: 'string' },
713
+ id: { type: 'string', description: 'Optional human-readable id.' },
714
+ type: {
715
+ type: 'string',
716
+ enum: ['trigger', 'agent', 'worker', 'context', 'condition', 'action', 'output'],
717
+ },
718
+ label: { type: 'string' },
719
+ config: { type: 'object', description: 'Arbitrary JSON (prompt, agent_name, etc.).' },
720
+ x: { type: 'number' },
721
+ y: { type: 'number' },
722
+ tier: { type: 'string' },
723
+ llm_profile: { type: 'string' },
724
+ tier_locked: { type: 'boolean' },
725
+ },
726
+ required: ['workflowId', 'type'],
727
+ additionalProperties: false,
728
+ },
729
+ },
730
+ {
731
+ name: 'byan_workflow_node_list',
732
+ description: 'List all nodes of a workflow, ordered by created_at.',
733
+ inputSchema: {
734
+ type: 'object',
735
+ properties: { workflowId: { type: 'string' } },
736
+ required: ['workflowId'],
737
+ additionalProperties: false,
738
+ },
739
+ },
740
+ {
741
+ name: 'byan_workflow_node_get',
742
+ description: 'Fetch a single node by id.',
743
+ inputSchema: {
744
+ type: 'object',
745
+ properties: { nodeId: { type: 'string' } },
746
+ required: ['nodeId'],
747
+ additionalProperties: false,
748
+ },
749
+ },
750
+ {
751
+ name: 'byan_workflow_node_update',
752
+ description:
753
+ 'Patch an existing node. Any of type, label, config, x, y, tier, llm_profile, tier_locked.',
754
+ inputSchema: {
755
+ type: 'object',
756
+ properties: {
757
+ nodeId: { type: 'string' },
758
+ type: {
759
+ type: 'string',
760
+ enum: ['trigger', 'agent', 'worker', 'context', 'condition', 'action', 'output'],
761
+ },
762
+ label: { type: 'string' },
763
+ config: { type: 'object' },
764
+ x: { type: 'number' },
765
+ y: { type: 'number' },
766
+ tier: { type: 'string' },
767
+ llm_profile: { type: 'string' },
768
+ tier_locked: { type: 'boolean' },
769
+ },
770
+ required: ['nodeId'],
771
+ additionalProperties: false,
772
+ },
773
+ },
774
+ {
775
+ name: 'byan_workflow_node_delete',
776
+ description:
777
+ 'Delete a node. Cascades to incoming/outgoing edges (via FK). Refuses deletion of the last trigger or output node of a workflow (409 LAST_OF_TYPE).',
778
+ inputSchema: {
779
+ type: 'object',
780
+ properties: { nodeId: { type: 'string' } },
781
+ required: ['nodeId'],
782
+ additionalProperties: false,
783
+ },
784
+ },
785
+ {
786
+ name: 'byan_workflow_edge_create',
787
+ description:
788
+ 'Create an edge between two nodes of the same workflow. condition_kind in {always, success, error, expr}; condition_expr required when kind=expr.',
789
+ inputSchema: {
790
+ type: 'object',
791
+ properties: {
792
+ workflowId: { type: 'string' },
793
+ from_node_id: { type: 'string' },
794
+ to_node_id: { type: 'string' },
795
+ condition_kind: { type: 'string', enum: ['always', 'success', 'error', 'expr'] },
796
+ condition_expr: { type: 'string' },
797
+ label: { type: 'string' },
798
+ },
799
+ required: ['workflowId', 'from_node_id', 'to_node_id'],
800
+ additionalProperties: false,
801
+ },
802
+ },
803
+ {
804
+ name: 'byan_workflow_edge_list',
805
+ description: 'List all edges of a workflow, ordered by created_at.',
806
+ inputSchema: {
807
+ type: 'object',
808
+ properties: { workflowId: { type: 'string' } },
809
+ required: ['workflowId'],
810
+ additionalProperties: false,
811
+ },
812
+ },
813
+ {
814
+ name: 'byan_workflow_edge_update',
815
+ description: 'Patch label / condition_kind / condition_expr of an edge.',
816
+ inputSchema: {
817
+ type: 'object',
818
+ properties: {
819
+ edgeId: { type: 'string' },
820
+ label: { type: 'string' },
821
+ condition_kind: { type: 'string', enum: ['always', 'success', 'error', 'expr'] },
822
+ condition_expr: { type: 'string' },
823
+ },
824
+ required: ['edgeId'],
825
+ additionalProperties: false,
826
+ },
827
+ },
828
+ {
829
+ name: 'byan_workflow_edge_delete',
830
+ description: 'Delete a single edge by id.',
831
+ inputSchema: {
832
+ type: 'object',
833
+ properties: { edgeId: { type: 'string' } },
834
+ required: ['edgeId'],
835
+ additionalProperties: false,
836
+ },
837
+ },
838
+ {
839
+ name: 'byan_workflow_edge_bulk_create',
840
+ description:
841
+ 'Atomically create multiple edges on a workflow. All-or-nothing : any FK or self-loop error rolls back the whole batch. Limit 500 edges per call.',
842
+ inputSchema: {
843
+ type: 'object',
844
+ properties: {
845
+ workflowId: { type: 'string' },
846
+ edges: {
847
+ type: 'array',
848
+ items: {
849
+ type: 'object',
850
+ properties: {
851
+ from_node_id: { type: 'string' },
852
+ to_node_id: { type: 'string' },
853
+ condition_kind: { type: 'string', enum: ['always', 'success', 'error', 'expr'] },
854
+ condition_expr: { type: 'string' },
855
+ label: { type: 'string' },
856
+ },
857
+ required: ['from_node_id', 'to_node_id'],
858
+ },
859
+ },
860
+ },
861
+ required: ['workflowId', 'edges'],
862
+ additionalProperties: false,
863
+ },
864
+ },
691
865
  ];
692
866
 
693
867
  const server = new Server(
@@ -1034,6 +1208,62 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1034
1208
  return { content: [{ type: 'text', text: JSON.stringify(r, null, 2) }] };
1035
1209
  }
1036
1210
 
1211
+ if (name === 'byan_workflow_node_create') {
1212
+ if (!BYAN_API_TOKEN) throw new Error('BYAN_API_TOKEN env var is required for this tool.');
1213
+ const r = await createNode({ apiRequest, ...args });
1214
+ return { content: [{ type: 'text', text: JSON.stringify(r, null, 2) }] };
1215
+ }
1216
+ if (name === 'byan_workflow_node_list') {
1217
+ if (!BYAN_API_TOKEN) throw new Error('BYAN_API_TOKEN env var is required for this tool.');
1218
+ const r = await listNodes({ apiRequest, workflowId: args.workflowId });
1219
+ return { content: [{ type: 'text', text: JSON.stringify(r, null, 2) }] };
1220
+ }
1221
+ if (name === 'byan_workflow_node_get') {
1222
+ if (!BYAN_API_TOKEN) throw new Error('BYAN_API_TOKEN env var is required for this tool.');
1223
+ const r = await getNode({ apiRequest, nodeId: args.nodeId });
1224
+ return { content: [{ type: 'text', text: JSON.stringify(r, null, 2) }] };
1225
+ }
1226
+ if (name === 'byan_workflow_node_update') {
1227
+ if (!BYAN_API_TOKEN) throw new Error('BYAN_API_TOKEN env var is required for this tool.');
1228
+ const r = await updateNode({ apiRequest, ...args });
1229
+ return { content: [{ type: 'text', text: JSON.stringify(r, null, 2) }] };
1230
+ }
1231
+ if (name === 'byan_workflow_node_delete') {
1232
+ if (!BYAN_API_TOKEN) throw new Error('BYAN_API_TOKEN env var is required for this tool.');
1233
+ const r = await deleteNode({ apiRequest, nodeId: args.nodeId });
1234
+ return { content: [{ type: 'text', text: JSON.stringify(r, null, 2) }] };
1235
+ }
1236
+
1237
+ if (name === 'byan_workflow_edge_create') {
1238
+ if (!BYAN_API_TOKEN) throw new Error('BYAN_API_TOKEN env var is required for this tool.');
1239
+ const r = await createEdge({ apiRequest, ...args });
1240
+ return { content: [{ type: 'text', text: JSON.stringify(r, null, 2) }] };
1241
+ }
1242
+ if (name === 'byan_workflow_edge_list') {
1243
+ if (!BYAN_API_TOKEN) throw new Error('BYAN_API_TOKEN env var is required for this tool.');
1244
+ const r = await listEdges({ apiRequest, workflowId: args.workflowId });
1245
+ return { content: [{ type: 'text', text: JSON.stringify(r, null, 2) }] };
1246
+ }
1247
+ if (name === 'byan_workflow_edge_update') {
1248
+ if (!BYAN_API_TOKEN) throw new Error('BYAN_API_TOKEN env var is required for this tool.');
1249
+ const r = await updateEdge({ apiRequest, ...args });
1250
+ return { content: [{ type: 'text', text: JSON.stringify(r, null, 2) }] };
1251
+ }
1252
+ if (name === 'byan_workflow_edge_delete') {
1253
+ if (!BYAN_API_TOKEN) throw new Error('BYAN_API_TOKEN env var is required for this tool.');
1254
+ const r = await deleteEdge({ apiRequest, edgeId: args.edgeId });
1255
+ return { content: [{ type: 'text', text: JSON.stringify(r, null, 2) }] };
1256
+ }
1257
+ if (name === 'byan_workflow_edge_bulk_create') {
1258
+ if (!BYAN_API_TOKEN) throw new Error('BYAN_API_TOKEN env var is required for this tool.');
1259
+ const r = await bulkCreateEdges({
1260
+ apiRequest,
1261
+ workflowId: args.workflowId,
1262
+ edges: args.edges,
1263
+ });
1264
+ return { content: [{ type: 'text', text: JSON.stringify(r, null, 2) }] };
1265
+ }
1266
+
1037
1267
  throw new Error(`Unknown tool: ${name}`);
1038
1268
  } catch (err) {
1039
1269
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-byan-agent",
3
- "version": "2.12.1",
3
+ "version": "2.13.0",
4
4
  "description": "BYAN v2.6.1 - Intelligent AI agent creator with ELO trust system + scientific fact-check + Hermes universal dispatcher. Multi-platform (Copilot CLI, Claude Code, Codex). Merise Agile + TDD + 64 Mantras. ~54% LLM cost savings.",
5
5
  "main": "src/index.js",
6
6
  "bin": {