devglide 0.1.1

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 (252) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +338 -0
  3. package/bin/claude-md-template.js +94 -0
  4. package/bin/devglide.js +387 -0
  5. package/package.json +85 -0
  6. package/pnpm-workspace.yaml +3 -0
  7. package/src/apps/coder/.turbo/turbo-lint.log +5 -0
  8. package/src/apps/coder/package.json +16 -0
  9. package/src/apps/coder/public/favicon.svg +7 -0
  10. package/src/apps/coder/public/page.css +275 -0
  11. package/src/apps/coder/public/page.js +528 -0
  12. package/src/apps/coder/server.js +3 -0
  13. package/src/apps/documentation/public/page.css +597 -0
  14. package/src/apps/documentation/public/page.js +609 -0
  15. package/src/apps/kanban/.turbo/turbo-lint.log +97 -0
  16. package/src/apps/kanban/.turbo/turbo-typecheck.log +5 -0
  17. package/src/apps/kanban/package.json +32 -0
  18. package/src/apps/kanban/public/favicon.svg +7 -0
  19. package/src/apps/kanban/public/page.css +1010 -0
  20. package/src/apps/kanban/public/page.js +1730 -0
  21. package/src/apps/kanban/public/vendor/marked.min.js +6 -0
  22. package/src/apps/kanban/public/vendor/sortable.min.js +2 -0
  23. package/src/apps/kanban/src/db.ts +319 -0
  24. package/src/apps/kanban/src/index.ts +14 -0
  25. package/src/apps/kanban/src/mcp-helpers.test.ts +88 -0
  26. package/src/apps/kanban/src/mcp-helpers.ts +60 -0
  27. package/src/apps/kanban/src/mcp.ts +59 -0
  28. package/src/apps/kanban/src/routes/attachments.ts +161 -0
  29. package/src/apps/kanban/src/routes/features.ts +233 -0
  30. package/src/apps/kanban/src/routes/issues.ts +373 -0
  31. package/src/apps/kanban/src/tools/feature-tools.ts +164 -0
  32. package/src/apps/kanban/src/tools/item-tools.ts +307 -0
  33. package/src/apps/kanban/src/tools/versioned-entry-tools.ts +72 -0
  34. package/src/apps/kanban/tsconfig.check.json +9 -0
  35. package/src/apps/kanban/tsconfig.json +9 -0
  36. package/src/apps/keymap/.turbo/turbo-lint.log +5 -0
  37. package/src/apps/keymap/package.json +16 -0
  38. package/src/apps/keymap/public/page.css +275 -0
  39. package/src/apps/keymap/public/page.js +294 -0
  40. package/src/apps/keymap/server.js +25 -0
  41. package/src/apps/log/.turbo/turbo-build.log +5 -0
  42. package/src/apps/log/.turbo/turbo-lint.log +45 -0
  43. package/src/apps/log/.turbo/turbo-typecheck.log +5 -0
  44. package/src/apps/log/node_modules/.bin/tsc +21 -0
  45. package/src/apps/log/node_modules/.bin/tsserver +21 -0
  46. package/src/apps/log/node_modules/.bin/tsx +21 -0
  47. package/src/apps/log/package.json +36 -0
  48. package/src/apps/log/public/console-sniffer.js +221 -0
  49. package/src/apps/log/public/favicon.svg +7 -0
  50. package/src/apps/log/public/page.css +322 -0
  51. package/src/apps/log/public/page.js +463 -0
  52. package/src/apps/log/src/index.ts +9 -0
  53. package/src/apps/log/src/mcp.ts +122 -0
  54. package/src/apps/log/src/routes/log.ts +333 -0
  55. package/src/apps/log/src/routes/status.ts +25 -0
  56. package/src/apps/log/src/server-sniffer.ts +118 -0
  57. package/src/apps/log/src/services/file-patterns.ts +39 -0
  58. package/src/apps/log/src/services/file-tailer.ts +228 -0
  59. package/src/apps/log/src/services/line-parser.ts +94 -0
  60. package/src/apps/log/src/services/log-writer.ts +39 -0
  61. package/src/apps/log/tsconfig.json +8 -0
  62. package/src/apps/prompts/.turbo/turbo-build.log +5 -0
  63. package/src/apps/prompts/.turbo/turbo-lint.log +24 -0
  64. package/src/apps/prompts/.turbo/turbo-typecheck.log +5 -0
  65. package/src/apps/prompts/mcp.ts +175 -0
  66. package/src/apps/prompts/node_modules/.bin/tsc +21 -0
  67. package/src/apps/prompts/node_modules/.bin/tsserver +21 -0
  68. package/src/apps/prompts/node_modules/.bin/tsx +21 -0
  69. package/src/apps/prompts/package.json +25 -0
  70. package/src/apps/prompts/public/page.css +315 -0
  71. package/src/apps/prompts/public/page.js +541 -0
  72. package/src/apps/prompts/services/prompt-store.ts +212 -0
  73. package/src/apps/prompts/src/index.ts +9 -0
  74. package/src/apps/prompts/tsconfig.json +8 -0
  75. package/src/apps/prompts/types.ts +27 -0
  76. package/src/apps/shell/.turbo/turbo-build.log +5 -0
  77. package/src/apps/shell/.turbo/turbo-lint.log +34 -0
  78. package/src/apps/shell/.turbo/turbo-typecheck.log +5 -0
  79. package/src/apps/shell/package.json +35 -0
  80. package/src/apps/shell/public/favicon.svg +7 -0
  81. package/src/apps/shell/public/page.css +407 -0
  82. package/src/apps/shell/public/page.js +1577 -0
  83. package/src/apps/shell/src/index.ts +150 -0
  84. package/src/apps/shell/src/mcp.ts +398 -0
  85. package/src/apps/shell/src/shell-types.ts +41 -0
  86. package/src/apps/shell/tsconfig.json +8 -0
  87. package/src/apps/test/.turbo/turbo-build.log +5 -0
  88. package/src/apps/test/.turbo/turbo-lint.log +27 -0
  89. package/src/apps/test/.turbo/turbo-typecheck.log +5 -0
  90. package/src/apps/test/node_modules/.bin/tsc +21 -0
  91. package/src/apps/test/node_modules/.bin/tsserver +21 -0
  92. package/src/apps/test/node_modules/.bin/tsx +21 -0
  93. package/src/apps/test/node_modules/.bin/uuid +21 -0
  94. package/src/apps/test/package.json +35 -0
  95. package/src/apps/test/public/favicon.svg +7 -0
  96. package/src/apps/test/public/page.css +499 -0
  97. package/src/apps/test/public/page.js +417 -0
  98. package/src/apps/test/public/scenario-runner.js +450 -0
  99. package/src/apps/test/src/index.ts +9 -0
  100. package/src/apps/test/src/mcp.ts +192 -0
  101. package/src/apps/test/src/routes/trigger.ts +285 -0
  102. package/src/apps/test/src/services/scenario-broadcaster.ts +60 -0
  103. package/src/apps/test/src/services/scenario-manager.ts +361 -0
  104. package/src/apps/test/src/services/scenario-store.ts +145 -0
  105. package/src/apps/test/tsconfig.json +8 -0
  106. package/src/apps/vocabulary/.turbo/turbo-build.log +5 -0
  107. package/src/apps/vocabulary/.turbo/turbo-lint.log +25 -0
  108. package/src/apps/vocabulary/.turbo/turbo-typecheck.log +5 -0
  109. package/src/apps/vocabulary/mcp.ts +173 -0
  110. package/src/apps/vocabulary/node_modules/.bin/tsc +21 -0
  111. package/src/apps/vocabulary/node_modules/.bin/tsserver +21 -0
  112. package/src/apps/vocabulary/node_modules/.bin/tsx +21 -0
  113. package/src/apps/vocabulary/package.json +25 -0
  114. package/src/apps/vocabulary/public/page.css +247 -0
  115. package/src/apps/vocabulary/public/page.js +444 -0
  116. package/src/apps/vocabulary/services/vocabulary-store.ts +179 -0
  117. package/src/apps/vocabulary/src/index.ts +10 -0
  118. package/src/apps/vocabulary/tsconfig.json +8 -0
  119. package/src/apps/vocabulary/types.ts +22 -0
  120. package/src/apps/voice/.turbo/turbo-build.log +5 -0
  121. package/src/apps/voice/.turbo/turbo-lint.log +43 -0
  122. package/src/apps/voice/.turbo/turbo-typecheck.log +5 -0
  123. package/src/apps/voice/node_modules/.bin/openai +21 -0
  124. package/src/apps/voice/node_modules/.bin/tsc +21 -0
  125. package/src/apps/voice/node_modules/.bin/tsserver +21 -0
  126. package/src/apps/voice/node_modules/.bin/tsx +21 -0
  127. package/src/apps/voice/package.json +35 -0
  128. package/src/apps/voice/public/favicon.svg +7 -0
  129. package/src/apps/voice/public/page.css +388 -0
  130. package/src/apps/voice/public/page.js +718 -0
  131. package/src/apps/voice/src/index.ts +10 -0
  132. package/src/apps/voice/src/mcp.ts +70 -0
  133. package/src/apps/voice/src/providers/index.ts +85 -0
  134. package/src/apps/voice/src/providers/openai-compatible.ts +94 -0
  135. package/src/apps/voice/src/providers/types.ts +27 -0
  136. package/src/apps/voice/src/routes/config.ts +118 -0
  137. package/src/apps/voice/src/routes/transcribe.ts +90 -0
  138. package/src/apps/voice/src/services/config-store.ts +129 -0
  139. package/src/apps/voice/src/services/stats.ts +108 -0
  140. package/src/apps/voice/src/transcribe.ts +11 -0
  141. package/src/apps/voice/src/utils/mime.ts +16 -0
  142. package/src/apps/voice/tsconfig.json +8 -0
  143. package/src/apps/workflow/.turbo/turbo-build.log +5 -0
  144. package/src/apps/workflow/.turbo/turbo-lint.log +96 -0
  145. package/src/apps/workflow/.turbo/turbo-typecheck.log +5 -0
  146. package/src/apps/workflow/engine/executors/decision-executor.ts +87 -0
  147. package/src/apps/workflow/engine/executors/file-executor.ts +90 -0
  148. package/src/apps/workflow/engine/executors/git-executor.ts +137 -0
  149. package/src/apps/workflow/engine/executors/http-executor.ts +65 -0
  150. package/src/apps/workflow/engine/executors/index.ts +28 -0
  151. package/src/apps/workflow/engine/executors/kanban-executor.ts +154 -0
  152. package/src/apps/workflow/engine/executors/llm-executor.ts +46 -0
  153. package/src/apps/workflow/engine/executors/log-executor.ts +62 -0
  154. package/src/apps/workflow/engine/executors/loop-executor.ts +14 -0
  155. package/src/apps/workflow/engine/executors/shell-executor.ts +107 -0
  156. package/src/apps/workflow/engine/executors/sub-workflow-executor.ts +61 -0
  157. package/src/apps/workflow/engine/executors/test-executor.ts +73 -0
  158. package/src/apps/workflow/engine/executors/trigger-executor.ts +39 -0
  159. package/src/apps/workflow/engine/expression-evaluator.ts +117 -0
  160. package/src/apps/workflow/engine/graph-runner.ts +438 -0
  161. package/src/apps/workflow/engine/node-executor.ts +104 -0
  162. package/src/apps/workflow/engine/node-registry.ts +15 -0
  163. package/src/apps/workflow/engine/variable-resolver.ts +109 -0
  164. package/src/apps/workflow/mcp.ts +223 -0
  165. package/src/apps/workflow/node_modules/.bin/tsc +21 -0
  166. package/src/apps/workflow/node_modules/.bin/tsserver +21 -0
  167. package/src/apps/workflow/node_modules/.bin/tsx +21 -0
  168. package/src/apps/workflow/package.json +25 -0
  169. package/src/apps/workflow/public/editor/canvas.js +366 -0
  170. package/src/apps/workflow/public/editor/drag-manager.js +326 -0
  171. package/src/apps/workflow/public/editor/edge-renderer.js +235 -0
  172. package/src/apps/workflow/public/editor/history-manager.js +147 -0
  173. package/src/apps/workflow/public/editor/layout-engine.js +159 -0
  174. package/src/apps/workflow/public/editor/node-renderer.js +199 -0
  175. package/src/apps/workflow/public/editor/selection-manager.js +193 -0
  176. package/src/apps/workflow/public/favicon.svg +7 -0
  177. package/src/apps/workflow/public/models/node-types.js +300 -0
  178. package/src/apps/workflow/public/models/workflow-model.js +257 -0
  179. package/src/apps/workflow/public/page.css +406 -0
  180. package/src/apps/workflow/public/page.js +658 -0
  181. package/src/apps/workflow/public/panels/inspector.js +360 -0
  182. package/src/apps/workflow/public/panels/palette.js +106 -0
  183. package/src/apps/workflow/public/panels/run-view.js +275 -0
  184. package/src/apps/workflow/public/panels/toolbar.js +232 -0
  185. package/src/apps/workflow/public/panels/workflow-list.js +237 -0
  186. package/src/apps/workflow/public/state/store.js +47 -0
  187. package/src/apps/workflow/services/custom-node-loader.ts +48 -0
  188. package/src/apps/workflow/services/legacy-converter.ts +72 -0
  189. package/src/apps/workflow/services/run-manager.ts +190 -0
  190. package/src/apps/workflow/services/workflow-store.ts +424 -0
  191. package/src/apps/workflow/services/workflow-validator.test.ts +103 -0
  192. package/src/apps/workflow/services/workflow-validator.ts +98 -0
  193. package/src/apps/workflow/src/index.ts +10 -0
  194. package/src/apps/workflow/templates/ci-pipeline.json +18 -0
  195. package/src/apps/workflow/templates/code-review.json +22 -0
  196. package/src/apps/workflow/templates/kanban-testing.json +24 -0
  197. package/src/apps/workflow/tsconfig.json +8 -0
  198. package/src/apps/workflow/types.ts +268 -0
  199. package/src/packages/auth-middleware.ts +14 -0
  200. package/src/packages/design-tokens/.turbo/turbo-build.log +10 -0
  201. package/src/packages/design-tokens/STYLEGUIDE.md +414 -0
  202. package/src/packages/design-tokens/build.js +413 -0
  203. package/src/packages/design-tokens/demo/index.html +1367 -0
  204. package/src/packages/design-tokens/demo/proposition-a.html +717 -0
  205. package/src/packages/design-tokens/demo/proposition-b.html +1239 -0
  206. package/src/packages/design-tokens/demo/proposition-c.html +1049 -0
  207. package/src/packages/design-tokens/dist/tailwind-preset.js +115 -0
  208. package/src/packages/design-tokens/dist/tokens.css +345 -0
  209. package/src/packages/design-tokens/dist/tokens.d.ts +229 -0
  210. package/src/packages/design-tokens/dist/tokens.js +386 -0
  211. package/src/packages/design-tokens/package.json +25 -0
  212. package/src/packages/design-tokens/tokens.json +228 -0
  213. package/src/packages/devtools-middleware.ts +22 -0
  214. package/src/packages/eslint-config/index.js +63 -0
  215. package/src/packages/eslint-config/node_modules/.bin/eslint +21 -0
  216. package/src/packages/eslint-config/package.json +18 -0
  217. package/src/packages/json-file-store.ts +232 -0
  218. package/src/packages/mcp-utils/.turbo/turbo-build.log +5 -0
  219. package/src/packages/mcp-utils/dist/index.d.ts +33 -0
  220. package/src/packages/mcp-utils/dist/index.d.ts.map +1 -0
  221. package/src/packages/mcp-utils/dist/index.js +126 -0
  222. package/src/packages/mcp-utils/dist/index.js.map +1 -0
  223. package/src/packages/mcp-utils/node_modules/.bin/tsc +21 -0
  224. package/src/packages/mcp-utils/node_modules/.bin/tsserver +21 -0
  225. package/src/packages/mcp-utils/package.json +32 -0
  226. package/src/packages/mcp-utils/src/index.ts +171 -0
  227. package/src/packages/mcp-utils/tsconfig.json +9 -0
  228. package/src/packages/paths.ts +18 -0
  229. package/src/packages/project-context/index.js +55 -0
  230. package/src/packages/project-context/package.json +13 -0
  231. package/src/packages/project-store.ts +127 -0
  232. package/src/packages/server-sniffer.ts +132 -0
  233. package/src/packages/shared-assets/favicon.svg +7 -0
  234. package/src/packages/shared-assets/keymap-registry.js +512 -0
  235. package/src/packages/shared-assets/logo.svg +6 -0
  236. package/src/packages/shared-assets/package.json +11 -0
  237. package/src/packages/shared-assets/ui-utils.js +48 -0
  238. package/src/packages/shared-assets/voice-widget.d.ts +37 -0
  239. package/src/packages/shared-assets/voice-widget.js +695 -0
  240. package/src/packages/shared-types/.turbo/turbo-build.log +5 -0
  241. package/src/packages/shared-types/dist/index.d.ts +39 -0
  242. package/src/packages/shared-types/dist/index.d.ts.map +1 -0
  243. package/src/packages/shared-types/node_modules/.bin/tsc +21 -0
  244. package/src/packages/shared-types/node_modules/.bin/tsserver +21 -0
  245. package/src/packages/shared-types/package.json +25 -0
  246. package/src/packages/shared-types/src/index.ts +41 -0
  247. package/src/packages/shared-types/tsconfig.json +11 -0
  248. package/src/packages/tsconfig/base.json +15 -0
  249. package/src/packages/tsconfig/next.json +14 -0
  250. package/src/packages/tsconfig/node.json +11 -0
  251. package/src/packages/tsconfig/package.json +10 -0
  252. package/turbo.json +25 -0
@@ -0,0 +1,223 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { WorkflowStore } from './services/workflow-store.js';
4
+ import { getActiveProject } from '../../project-context.js';
5
+ import type { Workflow, WorkflowNode, WorkflowEdge, VariableDefinition } from './types.js';
6
+ import { jsonResult, errorResult, createDevglideMcpServer } from '../../packages/mcp-utils/src/index.js';
7
+
8
+ // ── Zod schemas for validating MCP input ─────────────────────────────────────
9
+ const nodeInputSchema = z.array(z.object({
10
+ id: z.string(),
11
+ type: z.string(),
12
+ label: z.string(),
13
+ config: z.record(z.unknown()),
14
+ position: z.object({ x: z.number(), y: z.number() }),
15
+ }).passthrough());
16
+
17
+ const edgeInputSchema = z.array(z.object({
18
+ id: z.string(),
19
+ source: z.string(),
20
+ target: z.string(),
21
+ }).passthrough());
22
+
23
+ const variableInputSchema = z.array(z.object({
24
+ name: z.string(),
25
+ type: z.enum(['string', 'number', 'boolean', 'json']),
26
+ }).passthrough());
27
+
28
+ export function createWorkflowMcpServer(): McpServer {
29
+ const server = createDevglideMcpServer(
30
+ 'devglide-workflow',
31
+ '0.1.0',
32
+ 'Workflow engine for creating and managing passive instruction templates for LLM integration',
33
+ {
34
+ instructions: [
35
+ '## Workflow — Usage Conventions',
36
+ '',
37
+ '### Workflows',
38
+ '- Workflows are passive instruction templates that LLMs pick up automatically.',
39
+ '- They are directed acyclic graphs (DAGs) of nodes connected by edges.',
40
+ '- Node types include triggers, shell commands, kanban ops, git ops, tests, log, file I/O, LLM, HTTP, decisions, loops, and sub-workflows.',
41
+ '',
42
+ '### Matching workflows to prompts',
43
+ '- **Always** call `workflow_match` with the user prompt before responding to check if a workflow applies.',
44
+ '- If a match is found, follow the returned workflow instructions exactly.',
45
+ '- If no match is found, proceed normally.',
46
+ '',
47
+ '### Instructions',
48
+ '- Use `workflow_get_instructions` to get compiled instructions from all enabled workflows.',
49
+ '- Use `workflow_toggle` to enable or disable a workflow.',
50
+ '',
51
+ '### Managing workflows',
52
+ '- Use `workflow_list` to see all workflows.',
53
+ '- Use `workflow_get` to get a full workflow by ID.',
54
+ '- Use `workflow_create` to create a new workflow.',
55
+ ],
56
+ },
57
+ );
58
+
59
+ const store = WorkflowStore.getInstance();
60
+
61
+ // ── 1. workflow_list ──────────────────────────────────────────────────────
62
+
63
+ server.tool(
64
+ 'workflow_list',
65
+ 'List workflows visible to the current project context (project-scoped + global). Returns summaries with id, name, tags, node/edge counts. Pass projectId to scope to a specific project.',
66
+ {
67
+ projectId: z.string().optional().describe('Optional project ID to scope results. Defaults to the active project.'),
68
+ },
69
+ async ({ projectId }) => {
70
+ const scopeId = projectId ?? getActiveProject()?.id;
71
+ const workflows = await store.list(scopeId);
72
+ return jsonResult(workflows);
73
+ },
74
+ );
75
+
76
+ // ── 2. workflow_get ───────────────────────────────────────────────────────
77
+
78
+ server.tool(
79
+ 'workflow_get',
80
+ 'Get a workflow by ID. Returns the full workflow graph with nodes, edges, and variables.',
81
+ { id: z.string().describe('Workflow ID') },
82
+ async ({ id }) => {
83
+ const workflow = await store.get(id);
84
+ if (!workflow) return errorResult('Workflow not found');
85
+ return jsonResult(workflow);
86
+ },
87
+ );
88
+
89
+ // ── 3. workflow_create ────────────────────────────────────────────────────
90
+
91
+ server.tool(
92
+ 'workflow_create',
93
+ 'Create a new workflow. Nodes and edges are passed as JSON strings.',
94
+ {
95
+ name: z.string().describe('Workflow name'),
96
+ description: z.string().optional().describe('Workflow description'),
97
+ nodes: z.string().describe('JSON array of WorkflowNode objects'),
98
+ edges: z.string().describe('JSON array of WorkflowEdge objects'),
99
+ variables: z.string().optional().describe('JSON array of VariableDefinition objects'),
100
+ tags: z.string().optional().describe('JSON array of tag strings'),
101
+ },
102
+ async ({ name, description, nodes, edges, variables, tags }) => {
103
+ let parsedNodes: WorkflowNode[];
104
+ let parsedEdges: WorkflowEdge[];
105
+ let parsedVariables: VariableDefinition[] = [];
106
+ let parsedTags: string[] = [];
107
+
108
+ try {
109
+ const raw = JSON.parse(nodes);
110
+ const result = nodeInputSchema.safeParse(raw);
111
+ if (!result.success) return errorResult(`Invalid nodes: ${result.error.issues[0]?.message}`);
112
+ parsedNodes = result.data as unknown as WorkflowNode[];
113
+ } catch {
114
+ return errorResult('Invalid JSON for nodes');
115
+ }
116
+
117
+ try {
118
+ const raw = JSON.parse(edges);
119
+ const result = edgeInputSchema.safeParse(raw);
120
+ if (!result.success) return errorResult(`Invalid edges: ${result.error.issues[0]?.message}`);
121
+ parsedEdges = result.data as WorkflowEdge[];
122
+ } catch {
123
+ return errorResult('Invalid JSON for edges');
124
+ }
125
+
126
+ if (variables) {
127
+ try {
128
+ const raw = JSON.parse(variables);
129
+ const result = variableInputSchema.safeParse(raw);
130
+ if (!result.success) return errorResult(`Invalid variables: ${result.error.issues[0]?.message}`);
131
+ parsedVariables = result.data as VariableDefinition[];
132
+ } catch {
133
+ return errorResult('Invalid JSON for variables');
134
+ }
135
+ }
136
+
137
+ if (tags) {
138
+ try {
139
+ const raw = JSON.parse(tags);
140
+ const result = z.array(z.string()).safeParse(raw);
141
+ if (!result.success) return errorResult(`Invalid tags: ${result.error.issues[0]?.message}`);
142
+ parsedTags = result.data;
143
+ } catch {
144
+ return errorResult('Invalid JSON for tags');
145
+ }
146
+ }
147
+
148
+ const workflow = await store.save({
149
+ name,
150
+ description,
151
+ version: 1,
152
+ nodes: parsedNodes,
153
+ edges: parsedEdges,
154
+ variables: parsedVariables,
155
+ tags: parsedTags,
156
+ });
157
+
158
+ return jsonResult(workflow);
159
+ },
160
+ );
161
+
162
+ // ── 4. workflow_get_instructions ──────────────────────────────────────────
163
+
164
+ server.tool(
165
+ 'workflow_get_instructions',
166
+ 'Get compiled instructions from all enabled workflows as markdown. Optionally filter by projectId.',
167
+ {
168
+ projectId: z.string().optional().describe('Optional project ID to filter workflows'),
169
+ },
170
+ async ({ projectId }) => {
171
+ const scopeId = projectId ?? getActiveProject()?.id;
172
+ const markdown = await store.getCompiledInstructions(scopeId);
173
+ return {
174
+ content: [{ type: 'text' as const, text: markdown }],
175
+ };
176
+ },
177
+ );
178
+
179
+ // ── 5. workflow_match ───────────────────────────────────────────────────────
180
+
181
+ server.tool(
182
+ 'workflow_match',
183
+ 'Match a user prompt against all enabled workflows. Returns only workflows whose name, description, tags, or node content match the prompt, ranked by relevance. Use this before responding to check if a workflow applies.',
184
+ {
185
+ prompt: z.string().describe('The user prompt to match against workflows'),
186
+ projectId: z.string().optional().describe('Optional project ID to scope matching'),
187
+ },
188
+ async ({ prompt, projectId }) => {
189
+ const scopeId = projectId ?? getActiveProject()?.id;
190
+ const result = await store.match(prompt, scopeId);
191
+ if (result.matches.length === 0) {
192
+ return {
193
+ content: [{ type: 'text' as const, text: 'No matching workflows found.' }],
194
+ };
195
+ }
196
+ return jsonResult(result);
197
+ },
198
+ );
199
+
200
+ // ── 6. workflow_toggle ──────────────────────────────────────────────────────
201
+
202
+ server.tool(
203
+ 'workflow_toggle',
204
+ 'Toggle a workflow enabled/disabled. If currently enabled (or undefined), disables it. If disabled, enables it.',
205
+ {
206
+ id: z.string().describe('Workflow ID'),
207
+ },
208
+ async ({ id }) => {
209
+ const workflow = await store.get(id);
210
+ if (!workflow) return errorResult('Workflow not found');
211
+
212
+ const newEnabled = workflow.enabled === false ? true : false;
213
+ const updated = await store.save({
214
+ ...workflow,
215
+ enabled: newEnabled,
216
+ });
217
+
218
+ return jsonResult(updated);
219
+ },
220
+ );
221
+
222
+ return server;
223
+ }
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
19
+ else
20
+ exec node "$basedir/../typescript/bin/tsc" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
19
+ else
20
+ exec node "$basedir/../typescript/bin/tsserver" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/_work/devglide/devglide/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/tsx@4.21.0/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/_work/devglide/devglide/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/tsx@4.21.0/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../tsx/dist/cli.mjs" "$@"
19
+ else
20
+ exec node "$basedir/../tsx/dist/cli.mjs" "$@"
21
+ fi
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@devglide/workflow",
3
+ "version": "0.1.0",
4
+ "description": "Visual workflow builder with DAG execution engine",
5
+ "type": "module",
6
+ "main": "dist/src/index.js",
7
+ "scripts": {
8
+ "dev": "tsx watch src/index.ts",
9
+ "build": "tsc",
10
+ "start": "node dist/src/index.js",
11
+ "clean": "rm -rf dist",
12
+ "typecheck": "tsc --noEmit",
13
+ "lint": "eslint ."
14
+ },
15
+ "private": true,
16
+ "dependencies": {
17
+ "@devglide/mcp-utils": "workspace:*",
18
+ "@modelcontextprotocol/sdk": "^1.12.1",
19
+ "zod": "^3.25.49"
20
+ },
21
+ "devDependencies": {
22
+ "tsx": "^4.19.4",
23
+ "typescript": "^5.8.0"
24
+ }
25
+ }
@@ -0,0 +1,366 @@
1
+ // ── Workflow Editor — Zoomable/Pannable Canvas ─────────────────────────
2
+ // Creates a transformable world element with SVG edge overlay and dot-grid background.
3
+
4
+ import { store } from '../state/store.js';
5
+
6
+ const MIN_ZOOM = 0.25;
7
+ const MAX_ZOOM = 3.0;
8
+ const ZOOM_STEP = 0.1;
9
+
10
+ let _container = null;
11
+ let _root = null;
12
+ let _world = null;
13
+ let _svg = null;
14
+ let _unsubs = [];
15
+ let _isPanning = false;
16
+ let _panStart = { x: 0, y: 0 };
17
+ let _spaceHeld = false;
18
+
19
+ function buildDOM() {
20
+ _root = document.createElement('div');
21
+ _root.className = 'wfb-canvas';
22
+ _root.style.cssText = `
23
+ position: relative;
24
+ overflow: hidden;
25
+ width: 100%;
26
+ height: 100%;
27
+ background-color: var(--df-color-bg-base);
28
+ cursor: default;
29
+ `;
30
+ updateGridBackground();
31
+
32
+ // SVG overlay for edges
33
+ _svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
34
+ _svg.setAttribute('class', 'wfb-svg-layer');
35
+ _svg.style.cssText = `
36
+ position: absolute;
37
+ top: 0;
38
+ left: 0;
39
+ width: 100%;
40
+ height: 100%;
41
+ pointer-events: none;
42
+ overflow: visible;
43
+ `;
44
+
45
+ // Arrowhead marker definition
46
+ const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
47
+ const marker = document.createElementNS('http://www.w3.org/2000/svg', 'marker');
48
+ marker.setAttribute('id', 'wfb-arrowhead');
49
+ marker.setAttribute('viewBox', '0 0 10 7');
50
+ marker.setAttribute('refX', '10');
51
+ marker.setAttribute('refY', '3.5');
52
+ marker.setAttribute('markerWidth', '8');
53
+ marker.setAttribute('markerHeight', '6');
54
+ marker.setAttribute('orient', 'auto-start-reverse');
55
+ const arrow = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
56
+ arrow.setAttribute('points', '0 0, 10 3.5, 0 7');
57
+ arrow.setAttribute('fill', 'var(--df-color-border-default)');
58
+ marker.appendChild(arrow);
59
+ defs.appendChild(marker);
60
+
61
+ // Selected arrowhead
62
+ const markerSel = marker.cloneNode(true);
63
+ markerSel.setAttribute('id', 'wfb-arrowhead-selected');
64
+ markerSel.querySelector('polygon').setAttribute('fill', 'var(--df-color-accent-default)');
65
+ defs.appendChild(markerSel);
66
+
67
+ _svg.appendChild(defs);
68
+
69
+ // SVG group that transforms with the world
70
+ const svgWorld = document.createElementNS('http://www.w3.org/2000/svg', 'g');
71
+ svgWorld.setAttribute('class', 'wfb-svg-world');
72
+ _svg.appendChild(svgWorld);
73
+
74
+ // World element (nodes go here, transforms for pan/zoom)
75
+ _world = document.createElement('div');
76
+ _world.className = 'wfb-world';
77
+ _world.style.cssText = `
78
+ position: absolute;
79
+ top: 0;
80
+ left: 0;
81
+ transform-origin: 0 0;
82
+ will-change: transform;
83
+ `;
84
+
85
+ _root.appendChild(_svg);
86
+ _root.appendChild(_world);
87
+ return _root;
88
+ }
89
+
90
+ function updateGridBackground() {
91
+ if (!_root) return;
92
+ const zoom = store.get('zoom');
93
+ const panX = store.get('panX');
94
+ const panY = store.get('panY');
95
+ const size = 20 * zoom;
96
+ const ox = (panX % (20)) * zoom;
97
+ const oy = (panY % (20)) * zoom;
98
+ _root.style.backgroundImage = `radial-gradient(circle, var(--df-color-border-default) 1px, transparent 1px)`;
99
+ _root.style.backgroundSize = `${size}px ${size}px`;
100
+ _root.style.backgroundPosition = `${ox}px ${oy}px`;
101
+ }
102
+
103
+ function updateTransform() {
104
+ if (!_world || !_svg) return;
105
+ const zoom = store.get('zoom');
106
+ const panX = store.get('panX');
107
+ const panY = store.get('panY');
108
+ _world.style.transform = `translate(${panX}px, ${panY}px) scale(${zoom})`;
109
+
110
+ // Update SVG world group to match
111
+ const svgWorld = _svg.querySelector('.wfb-svg-world');
112
+ if (svgWorld) {
113
+ svgWorld.setAttribute('transform', `translate(${panX}, ${panY}) scale(${zoom})`);
114
+ }
115
+
116
+ updateGridBackground();
117
+ }
118
+
119
+ // ── Event handlers ──────────────────────────────────────────────────────
120
+
121
+ function onWheel(e) {
122
+ e.preventDefault();
123
+ const zoom = store.get('zoom');
124
+ const delta = -Math.sign(e.deltaY) * ZOOM_STEP;
125
+ const newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, zoom + delta));
126
+ if (newZoom === zoom) return;
127
+
128
+ // Zoom toward cursor
129
+ const rect = _root.getBoundingClientRect();
130
+ const cx = e.clientX - rect.left;
131
+ const cy = e.clientY - rect.top;
132
+ const panX = store.get('panX');
133
+ const panY = store.get('panY');
134
+
135
+ const scale = newZoom / zoom;
136
+ const newPanX = cx - (cx - panX) * scale;
137
+ const newPanY = cy - (cy - panY) * scale;
138
+
139
+ store.set('zoom', newZoom);
140
+ store.set('panX', newPanX);
141
+ store.set('panY', newPanY);
142
+ updateTransform();
143
+ }
144
+
145
+ function onPointerDown(e) {
146
+ // Middle mouse button or space+left for panning
147
+ if (e.button === 1 || (_spaceHeld && e.button === 0)) {
148
+ e.preventDefault();
149
+ _isPanning = true;
150
+ _panStart = { x: e.clientX - store.get('panX'), y: e.clientY - store.get('panY') };
151
+ _root.style.cursor = 'grabbing';
152
+ _root.setPointerCapture(e.pointerId);
153
+ }
154
+ }
155
+
156
+ function onPointerMove(e) {
157
+ if (!_isPanning) return;
158
+ const panX = e.clientX - _panStart.x;
159
+ const panY = e.clientY - _panStart.y;
160
+ store.set('panX', panX);
161
+ store.set('panY', panY);
162
+ updateTransform();
163
+ }
164
+
165
+ function onPointerUp(e) {
166
+ if (_isPanning) {
167
+ _isPanning = false;
168
+ _root.style.cursor = _spaceHeld ? 'grab' : 'default';
169
+ _root.releasePointerCapture(e.pointerId);
170
+ }
171
+ }
172
+
173
+ function onKeyDown(e) {
174
+ if (e.code === 'Space' && !e.repeat && document.activeElement === document.body) {
175
+ e.preventDefault();
176
+ _spaceHeld = true;
177
+ if (_root) _root.style.cursor = 'grab';
178
+ }
179
+ }
180
+
181
+ function onKeyUp(e) {
182
+ if (e.code === 'Space') {
183
+ _spaceHeld = false;
184
+ if (_root && !_isPanning) _root.style.cursor = 'default';
185
+ }
186
+ }
187
+
188
+ // ── Public API ──────────────────────────────────────────────────────────
189
+
190
+ export const Canvas = {
191
+ /**
192
+ * Mount the canvas into a container.
193
+ * @param {HTMLElement} container
194
+ */
195
+ mount(container) {
196
+ _container = container;
197
+ const root = buildDOM();
198
+ container.appendChild(root);
199
+
200
+ // Attach event listeners
201
+ _root.addEventListener('wheel', onWheel, { passive: false });
202
+ _root.addEventListener('pointerdown', onPointerDown);
203
+ _root.addEventListener('pointermove', onPointerMove);
204
+ _root.addEventListener('pointerup', onPointerUp);
205
+ _root.addEventListener('pointercancel', onPointerUp);
206
+ document.addEventListener('keydown', onKeyDown);
207
+ document.addEventListener('keyup', onKeyUp);
208
+
209
+ // Subscribe to store changes
210
+ _unsubs.push(store.on('zoom', () => updateTransform()));
211
+ _unsubs.push(store.on('panX', () => updateTransform()));
212
+ _unsubs.push(store.on('panY', () => updateTransform()));
213
+
214
+ updateTransform();
215
+ },
216
+
217
+ /**
218
+ * Remove listeners and clear DOM.
219
+ */
220
+ unmount() {
221
+ if (_root) {
222
+ _root.removeEventListener('wheel', onWheel);
223
+ _root.removeEventListener('pointerdown', onPointerDown);
224
+ _root.removeEventListener('pointermove', onPointerMove);
225
+ _root.removeEventListener('pointerup', onPointerUp);
226
+ _root.removeEventListener('pointercancel', onPointerUp);
227
+ }
228
+ document.removeEventListener('keydown', onKeyDown);
229
+ document.removeEventListener('keyup', onKeyUp);
230
+
231
+ for (const unsub of _unsubs) unsub();
232
+ _unsubs = [];
233
+
234
+ if (_root && _container) {
235
+ _container.removeChild(_root);
236
+ }
237
+ _root = null;
238
+ _world = null;
239
+ _svg = null;
240
+ _container = null;
241
+ _isPanning = false;
242
+ _spaceHeld = false;
243
+ },
244
+
245
+ /**
246
+ * Convert world coordinates to screen coordinates.
247
+ * @param {number} x
248
+ * @param {number} y
249
+ * @returns {{ x: number, y: number }}
250
+ */
251
+ worldToScreen(x, y) {
252
+ const zoom = store.get('zoom');
253
+ const panX = store.get('panX');
254
+ const panY = store.get('panY');
255
+ return {
256
+ x: x * zoom + panX,
257
+ y: y * zoom + panY,
258
+ };
259
+ },
260
+
261
+ /**
262
+ * Convert screen coordinates to world coordinates (for drops).
263
+ * @param {number} sx
264
+ * @param {number} sy
265
+ * @returns {{ x: number, y: number }}
266
+ */
267
+ screenToWorld(sx, sy) {
268
+ const zoom = store.get('zoom');
269
+ const panX = store.get('panX');
270
+ const panY = store.get('panY');
271
+ const rect = _root?.getBoundingClientRect() ?? { left: 0, top: 0 };
272
+ return {
273
+ x: (sx - rect.left - panX) / zoom,
274
+ y: (sy - rect.top - panY) / zoom,
275
+ };
276
+ },
277
+
278
+ /**
279
+ * Returns the world element where nodes are placed.
280
+ * @returns {HTMLElement|null}
281
+ */
282
+ getWorldElement() {
283
+ return _world;
284
+ },
285
+
286
+ /**
287
+ * Returns the SVG world group where edges are drawn.
288
+ * @returns {SVGGElement|null}
289
+ */
290
+ getSvgElement() {
291
+ return _svg?.querySelector('.wfb-svg-world') ?? null;
292
+ },
293
+
294
+ /**
295
+ * Returns the root canvas element.
296
+ * @returns {HTMLElement|null}
297
+ */
298
+ getRootElement() {
299
+ return _root;
300
+ },
301
+
302
+ /**
303
+ * Set the zoom level, optionally zooming toward a screen-space point.
304
+ * @param {number} zoom
305
+ * @param {number} [cx] - Screen X to zoom toward
306
+ * @param {number} [cy] - Screen Y to zoom toward
307
+ */
308
+ setZoom(zoom, cx, cy) {
309
+ const clamped = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, zoom));
310
+ if (cx != null && cy != null) {
311
+ const oldZoom = store.get('zoom');
312
+ const panX = store.get('panX');
313
+ const panY = store.get('panY');
314
+ const rect = _root?.getBoundingClientRect() ?? { left: 0, top: 0 };
315
+ const px = cx - rect.left;
316
+ const py = cy - rect.top;
317
+ const scale = clamped / oldZoom;
318
+ store.set('panX', px - (px - panX) * scale);
319
+ store.set('panY', py - (py - panY) * scale);
320
+ }
321
+ store.set('zoom', clamped);
322
+ updateTransform();
323
+ },
324
+
325
+ /**
326
+ * Calculate bounds and set zoom/pan to fit all given nodes with padding.
327
+ * @param {Array<{ position: { x: number, y: number } }>} nodes
328
+ */
329
+ fitToView(nodes) {
330
+ if (!_root || !nodes.length) return;
331
+
332
+ const NODE_WIDTH = 220;
333
+ const NODE_HEIGHT = 80;
334
+ const PADDING = 50;
335
+
336
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
337
+ for (const n of nodes) {
338
+ minX = Math.min(minX, n.position.x);
339
+ minY = Math.min(minY, n.position.y);
340
+ maxX = Math.max(maxX, n.position.x + NODE_WIDTH);
341
+ maxY = Math.max(maxY, n.position.y + NODE_HEIGHT);
342
+ }
343
+
344
+ const boundsW = maxX - minX + PADDING * 2;
345
+ const boundsH = maxY - minY + PADDING * 2;
346
+ const rect = _root.getBoundingClientRect();
347
+ const canvasW = rect.width || 800;
348
+ const canvasH = rect.height || 600;
349
+
350
+ const zoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, Math.min(canvasW / boundsW, canvasH / boundsH)));
351
+ const panX = (canvasW - boundsW * zoom) / 2 - (minX - PADDING) * zoom;
352
+ const panY = (canvasH - boundsH * zoom) / 2 - (minY - PADDING) * zoom;
353
+
354
+ store.set('zoom', zoom);
355
+ store.set('panX', panX);
356
+ store.set('panY', panY);
357
+ updateTransform();
358
+ },
359
+
360
+ /**
361
+ * Force-refresh the canvas transform.
362
+ */
363
+ refresh() {
364
+ updateTransform();
365
+ },
366
+ };