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,450 @@
1
+ (function () {
2
+ 'use strict';
3
+
4
+ // ---- 1. Extract config — injected global or script-tag src ----
5
+
6
+ var serverOrigin, targetPath;
7
+
8
+ var _cfg = window.__devglideRunnerConfig;
9
+ if (_cfg) {
10
+ serverOrigin = _cfg.serverOrigin;
11
+ targetPath = _cfg.target;
12
+ delete window.__devglideRunnerConfig;
13
+ } else {
14
+ var scriptEl = document.currentScript;
15
+ if (!scriptEl) {
16
+ var scripts = document.getElementsByTagName('script');
17
+ for (var i = 0; i < scripts.length; i++) {
18
+ var src = scripts[i].getAttribute('src') || '';
19
+ if (src.indexOf('scenario-runner.js') !== -1) {
20
+ scriptEl = scripts[i];
21
+ break;
22
+ }
23
+ }
24
+ }
25
+
26
+ if (!scriptEl) {
27
+ return;
28
+ }
29
+
30
+ var fullSrc = scriptEl.getAttribute('src');
31
+
32
+ var originMatch = fullSrc.match(/^(https?:\/\/[^\/]+)/);
33
+ if (!originMatch) {
34
+ return;
35
+ }
36
+ serverOrigin = originMatch[1];
37
+
38
+ targetPath = '';
39
+ var qIndex = fullSrc.indexOf('?');
40
+ if (qIndex !== -1) {
41
+ var query = fullSrc.substring(qIndex + 1);
42
+ var params = query.split('&');
43
+ for (var p = 0; p < params.length; p++) {
44
+ var kv = params[p].split('=');
45
+ var key = kv[0];
46
+ var val = decodeURIComponent(kv.slice(1).join('='));
47
+ if (key === 'target') {
48
+ targetPath = val;
49
+ }
50
+ }
51
+ }
52
+ }
53
+
54
+ if (!targetPath) {
55
+ return;
56
+ }
57
+
58
+ // ---- 2. Trigger module ----
59
+
60
+ var DEFAULT_TIMEOUT = 5000;
61
+
62
+ var LS_KEY = 'cs-trigger-scenario';
63
+
64
+ function saveProgress(scenario, nextStepIndex) {
65
+ try {
66
+ localStorage.setItem(LS_KEY, JSON.stringify({
67
+ scenarioId: scenario.id,
68
+ scenarioName: scenario.name,
69
+ steps: scenario.steps,
70
+ nextStepIndex: nextStepIndex,
71
+ target: scenario.target || null,
72
+ savedAt: new Date().toISOString()
73
+ }));
74
+ } catch (e) {
75
+ console.warn('[scenario-runner] Cannot save progress to localStorage: ' + e.message);
76
+ }
77
+ }
78
+
79
+ function clearProgress() {
80
+ try {
81
+ localStorage.removeItem(LS_KEY);
82
+ } catch (e) {
83
+ // ignore
84
+ }
85
+ }
86
+
87
+ function loadProgress() {
88
+ try {
89
+ var raw = localStorage.getItem(LS_KEY);
90
+ if (!raw) return null;
91
+ var data = JSON.parse(raw);
92
+ if (data.savedAt) {
93
+ var elapsed = Date.now() - new Date(data.savedAt).getTime();
94
+ if (elapsed > 5 * 60 * 1000) {
95
+ localStorage.removeItem(LS_KEY);
96
+ return null;
97
+ }
98
+ }
99
+ return data;
100
+ } catch (e) {
101
+ return null;
102
+ }
103
+ }
104
+
105
+ function resolveElement(selector) {
106
+ var el = document.querySelector(selector);
107
+ if (!el) {
108
+ throw new Error('Element not found: ' + selector);
109
+ }
110
+ return el;
111
+ }
112
+
113
+ function waitForElement(selector, timeout) {
114
+ var deadline = Date.now() + (timeout || DEFAULT_TIMEOUT);
115
+ return new Promise(function (resolve, reject) {
116
+ var check = function () {
117
+ var el = document.querySelector(selector);
118
+ if (el) {
119
+ resolve(el);
120
+ } else if (Date.now() >= deadline) {
121
+ reject(new Error('Timed out waiting for element: ' + selector));
122
+ } else {
123
+ setTimeout(check, 50);
124
+ }
125
+ };
126
+ check();
127
+ });
128
+ }
129
+
130
+ function waitForElementHidden(selector, timeout) {
131
+ var deadline = Date.now() + (timeout || DEFAULT_TIMEOUT);
132
+ return new Promise(function (resolve, reject) {
133
+ var check = function () {
134
+ var el = document.querySelector(selector);
135
+ if (!el || el.offsetParent === null) {
136
+ resolve();
137
+ } else if (Date.now() >= deadline) {
138
+ reject(new Error('Timed out waiting for element to hide: ' + selector));
139
+ } else {
140
+ setTimeout(check, 50);
141
+ }
142
+ };
143
+ check();
144
+ });
145
+ }
146
+
147
+ function dispatchInputEvents(el) {
148
+ el.dispatchEvent(new Event('input', { bubbles: true }));
149
+ el.dispatchEvent(new Event('change', { bubbles: true }));
150
+ }
151
+
152
+ var executors = {
153
+ click: function (step) {
154
+ var el = resolveElement(step.selector);
155
+ el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
156
+ return Promise.resolve();
157
+ },
158
+
159
+ dblclick: function (step) {
160
+ var el = resolveElement(step.selector);
161
+ el.dispatchEvent(new MouseEvent('dblclick', { bubbles: true, cancelable: true }));
162
+ return Promise.resolve();
163
+ },
164
+
165
+ type: function (step) {
166
+ var el = resolveElement(step.selector);
167
+ var descriptor = Object.getOwnPropertyDescriptor(
168
+ Object.getPrototypeOf(el).constructor.prototype || HTMLInputElement.prototype, 'value'
169
+ );
170
+ var setValue = (descriptor && descriptor.set)
171
+ ? function (v) { descriptor.set.call(el, v); }
172
+ : function (v) { el.value = v; };
173
+
174
+ if (step.clear !== false) {
175
+ setValue('');
176
+ dispatchInputEvents(el);
177
+ }
178
+ setValue(step.text || '');
179
+ dispatchInputEvents(el);
180
+ return Promise.resolve();
181
+ },
182
+
183
+ select: function (step) {
184
+ var el = resolveElement(step.selector);
185
+ el.value = step.value;
186
+ el.dispatchEvent(new Event('change', { bubbles: true }));
187
+ return Promise.resolve();
188
+ },
189
+
190
+ wait: function (step) {
191
+ return new Promise(function (resolve) {
192
+ setTimeout(resolve, step.ms || 0);
193
+ });
194
+ },
195
+
196
+ waitFor: function (step) {
197
+ return waitForElement(step.selector, step.timeout);
198
+ },
199
+
200
+ waitForHidden: function (step) {
201
+ return waitForElementHidden(step.selector, step.timeout);
202
+ },
203
+
204
+ find: function (step) { // alias for waitFor
205
+ return waitForElement(step.selector, step.timeout);
206
+ },
207
+
208
+ assertExists: function (step) {
209
+ resolveElement(step.selector);
210
+ return Promise.resolve();
211
+ },
212
+
213
+ assertText: function (step) {
214
+ var el = resolveElement(step.selector);
215
+ var actual = (el.textContent || '').trim();
216
+ var expected = step.text || '';
217
+ var isContains = step.contains !== false; // default true
218
+ if (isContains) {
219
+ if (actual.indexOf(expected) === -1) {
220
+ throw new Error('assertText failed: "' + actual + '" does not contain "' + expected + '"');
221
+ }
222
+ } else {
223
+ if (actual !== expected) {
224
+ throw new Error('assertText failed: expected "' + expected + '" but got "' + actual + '"');
225
+ }
226
+ }
227
+ return Promise.resolve();
228
+ },
229
+
230
+ logPath: function () {
231
+ console.log('[scenario-runner] Current path: ' + window.location.href);
232
+ return Promise.resolve();
233
+ },
234
+
235
+ logBody: function () {
236
+ console.log('[scenario-runner] Current body HTML: ' + document.body.innerHTML);
237
+ return Promise.resolve();
238
+ },
239
+
240
+ logHead: function () {
241
+ console.log('[scenario-runner] Current head HTML: ' + document.head.innerHTML);
242
+ return Promise.resolve();
243
+ },
244
+
245
+ navigate: function (step, scenario, stepIndex) {
246
+ if (typeof step.path !== 'string' || !step.path.startsWith('/') || step.path.indexOf('//') !== -1) {
247
+ return Promise.reject(new Error('navigate: invalid path "' + step.path + '". Must be a relative path starting with "/" and must not contain "//"'));
248
+ }
249
+ saveProgress(scenario, stepIndex + 1);
250
+ window.location.href = step.path;
251
+ return new Promise(function () {});
252
+ }
253
+ };
254
+
255
+ var resultUrl = serverOrigin + '/api/test/trigger/scenarios/';
256
+
257
+ function reportResult(scenarioId, status, failedStep, error, duration) {
258
+ var body = { status: status };
259
+ if (typeof failedStep === 'number') body.failedStep = failedStep;
260
+ if (error) body.error = error;
261
+ if (typeof duration === 'number') body.duration = duration;
262
+ try {
263
+ fetch(resultUrl + encodeURIComponent(scenarioId) + '/result', {
264
+ method: 'POST',
265
+ headers: { 'Content-Type': 'application/json' },
266
+ body: JSON.stringify(body)
267
+ }).catch(function (err) {
268
+ console.warn('[scenario-runner] Failed to report result: ' + err.message);
269
+ });
270
+ } catch (e) {
271
+ // ignore — fire-and-forget
272
+ }
273
+ }
274
+
275
+ function runScenario(scenario, startIndex) {
276
+ var steps = scenario.steps || [];
277
+ var i = startIndex || 0;
278
+ var startTime = Date.now();
279
+
280
+ function nextStep() {
281
+ if (i >= steps.length) {
282
+ return Promise.resolve();
283
+ }
284
+ var step = steps[i];
285
+ var currentIndex = i;
286
+ i++;
287
+ var executor = executors[step.command];
288
+ if (!executor) {
289
+ var err = new Error('Unknown command: ' + step.command);
290
+ err._failedStep = currentIndex;
291
+ return Promise.reject(err);
292
+ }
293
+ try {
294
+ return executor(step, scenario, currentIndex).then(function () {
295
+ return nextStep();
296
+ }, function (err) {
297
+ err._failedStep = currentIndex;
298
+ return Promise.reject(err);
299
+ });
300
+ } catch (e) {
301
+ e._failedStep = currentIndex;
302
+ return Promise.reject(e);
303
+ }
304
+ }
305
+
306
+ return nextStep().then(function () {
307
+ clearProgress();
308
+ var duration = Date.now() - startTime;
309
+ reportResult(scenario.id, 'passed', undefined, undefined, duration);
310
+ }, function (err) {
311
+ clearProgress();
312
+ var duration = Date.now() - startTime;
313
+ reportResult(scenario.id, 'failed', err._failedStep, err.message, duration);
314
+ throw err;
315
+ });
316
+ }
317
+
318
+ // ---- 3. Scenario delivery — SSE primary, HTTP poll fallback ----
319
+
320
+ var streamUrl = serverOrigin + '/api/test/trigger/scenarios/stream?target=' + encodeURIComponent(targetPath).replace(/%2F/gi, '/');
321
+ var pollUrl = serverOrigin + '/api/test/trigger/scenarios/poll?target=' + encodeURIComponent(targetPath).replace(/%2F/gi, '/');
322
+
323
+ // Shared handler: process a scenario received from either SSE or poll
324
+ function handleScenario(scenario) {
325
+ return runScenario(scenario).then(function () {
326
+ console.log('[scenario-runner] Scenario completed: ' + (scenario.name || scenario.id));
327
+ }).catch(function (err) {
328
+ console.error('[scenario-runner] Scenario failed: ' + err.message);
329
+ });
330
+ }
331
+
332
+ // ---- SSE stream (primary mechanism) ----
333
+
334
+ var eventSource = null;
335
+
336
+ function connectSSE() {
337
+ eventSource = new EventSource(streamUrl);
338
+
339
+ eventSource.onmessage = function (event) {
340
+ var scenario;
341
+ try {
342
+ scenario = JSON.parse(event.data);
343
+ } catch (e) {
344
+ console.warn('[scenario-runner] Failed to parse SSE data: ' + e.message);
345
+ return;
346
+ }
347
+ if (!scenario || !scenario.steps) return;
348
+ handleScenario(scenario);
349
+ };
350
+
351
+ eventSource.onerror = function () {
352
+ // EventSource automatically reconnects on error.
353
+ // Log only once per error event to avoid spam.
354
+ console.warn('[scenario-runner] SSE connection error — reconnecting...');
355
+ };
356
+ }
357
+
358
+ // ---- HTTP poll fallback (used only if EventSource is unavailable) ----
359
+
360
+ var POLL_INTERVAL = 30000;
361
+ var retryDelay = 2000;
362
+ var maxRetryDelay = 30000;
363
+ var pollTimer = null;
364
+
365
+ function pollLoop() {
366
+ // Don't poll while the tab is hidden — resume on visibility change
367
+ if (document.hidden) return;
368
+
369
+ fetch(pollUrl).then(function (response) {
370
+ if (response.status === 204) {
371
+ retryDelay = 2000;
372
+ pollTimer = setTimeout(pollLoop, POLL_INTERVAL);
373
+ return;
374
+ }
375
+ if (!response.ok) {
376
+ throw new Error('Poll returned status ' + response.status);
377
+ }
378
+ return response.json();
379
+ }).then(function (scenario) {
380
+ if (!scenario) {
381
+ return;
382
+ }
383
+ retryDelay = 2000;
384
+ return handleScenario(scenario).then(function () {
385
+ pollLoop();
386
+ });
387
+ }).catch(function (err) {
388
+ console.warn('[scenario-runner] Poll error, retrying in ' + retryDelay + 'ms: ' + err.message);
389
+ pollTimer = setTimeout(function () {
390
+ retryDelay = Math.min(retryDelay * 2, maxRetryDelay);
391
+ pollLoop();
392
+ }, retryDelay);
393
+ });
394
+ }
395
+
396
+ // Pause polling when tab is hidden, resume when visible (poll fallback only)
397
+ function onVisibilityChangePoll() {
398
+ if (!document.hidden) {
399
+ clearTimeout(pollTimer);
400
+ pollLoop();
401
+ }
402
+ }
403
+
404
+ // ---- Start listening ----
405
+
406
+ function startListening() {
407
+ if (typeof EventSource !== 'undefined') {
408
+ connectSSE();
409
+ } else {
410
+ // Fallback for environments without EventSource support
411
+ console.warn('[scenario-runner] EventSource not available — falling back to HTTP polling');
412
+ document.addEventListener('visibilitychange', onVisibilityChangePoll);
413
+ pollLoop();
414
+ }
415
+ }
416
+
417
+ function tryResumeScenario() {
418
+ var saved = loadProgress();
419
+ if (!saved) return false;
420
+ var savedTarget = saved.target || null;
421
+ if (savedTarget !== targetPath) {
422
+ clearProgress();
423
+ return false;
424
+ }
425
+ if (!saved.steps || saved.nextStepIndex >= saved.steps.length) {
426
+ clearProgress();
427
+ return false;
428
+ }
429
+ console.log('[scenario-runner] Resuming scenario "' + (saved.scenarioName || saved.scenarioId) + '" from step ' + saved.nextStepIndex);
430
+ var scenario = {
431
+ id: saved.scenarioId,
432
+ name: saved.scenarioName,
433
+ steps: saved.steps,
434
+ target: saved.target
435
+ };
436
+ runScenario(scenario, saved.nextStepIndex).then(function () {
437
+ console.log('[scenario-runner] Scenario completed: ' + (scenario.name || scenario.id));
438
+ }).catch(function (err) {
439
+ console.error('[scenario-runner] Scenario failed: ' + err.message);
440
+ }).then(function () {
441
+ startListening();
442
+ });
443
+ return true;
444
+ }
445
+
446
+ if (!tryResumeScenario()) {
447
+ startListening();
448
+ }
449
+
450
+ })();
@@ -0,0 +1,9 @@
1
+ import { createTestMcpServer } from "./mcp.js";
2
+ import { runStdio } from "@devglide/mcp-utils";
3
+
4
+ // ── Stdio MCP mode ──────────────────────────────────────────────────────────
5
+ if (process.argv.includes("--stdio")) {
6
+ const mcpServer = createTestMcpServer();
7
+ await runStdio(mcpServer);
8
+ console.error("Devglide Test MCP server running on stdio");
9
+ }
@@ -0,0 +1,192 @@
1
+ import { z } from "zod";
2
+ import { createDevglideMcpServer } from "../../../packages/mcp-utils/src/index.js";
3
+ import { ScenarioManager } from "./services/scenario-manager.js";
4
+ import { ScenarioStore } from "./services/scenario-store.js";
5
+
6
+ const UNIFIED_BASE = `http://localhost:${process.env.PORT || 7000}`;
7
+
8
+ /** POST/GET helper that proxies to the unified server's HTTP API */
9
+ async function unifiedFetch(path: string, method: "GET" | "POST" = "GET", body?: unknown): Promise<Response> {
10
+ const opts: RequestInit = {
11
+ method,
12
+ headers: { "Content-Type": "application/json" },
13
+ };
14
+ if (body) opts.body = JSON.stringify(body);
15
+ return fetch(`${UNIFIED_BASE}${path}`, opts);
16
+ }
17
+
18
+ export function createTestMcpServer() {
19
+ const server = createDevglideMcpServer(
20
+ "devglide-test",
21
+ "0.1.0",
22
+ "Browser UI automation and scenario execution. " +
23
+ "External apps enable automation via <script src=\"http://localhost:7000/devtools.js?target=/path/to/app\"></script>. " +
24
+ "DevGlide monorepo apps are handled by the unified server and need no setup. " +
25
+ "Targets can be absolute paths or simple app names (e.g. 'kanban', 'dashboard') " +
26
+ "which are resolved automatically from known polling browsers."
27
+ );
28
+ const scenarioManager = ScenarioManager.getInstance();
29
+
30
+ server.tool(
31
+ "test_commands",
32
+ "List available browser automation commands",
33
+ {},
34
+ async () => {
35
+ const catalog = scenarioManager.getCommandsCatalog();
36
+ return {
37
+ content: [{ type: "text" as const, text: JSON.stringify(catalog, null, 2) }],
38
+ };
39
+ }
40
+ );
41
+
42
+ server.tool(
43
+ "test_run_scenario",
44
+ "Submit a UI automation scenario for browser execution. The browser must have devtools.js loaded (via the unified server's devtools.js?target= endpoint).",
45
+ {
46
+ name: z.string().optional().describe("Scenario name"),
47
+ description: z.string().optional().describe("Scenario description"),
48
+ target: z
49
+ .string()
50
+ .optional()
51
+ .describe(
52
+ "Target identifier — can be an absolute path (matches devtools.js?target= param) or a simple app name (e.g. 'kanban', 'dashboard') which is resolved automatically"
53
+ ),
54
+ steps: z
55
+ .array(
56
+ z.object({
57
+ command: z.string().describe("Command name"),
58
+ selector: z.string().optional(),
59
+ text: z.string().optional(),
60
+ value: z.string().optional(),
61
+ timeout: z.number().optional(),
62
+ ms: z.number().optional(),
63
+ clear: z.boolean().optional(),
64
+ contains: z.boolean().optional(),
65
+ path: z.string().optional(),
66
+ })
67
+ )
68
+ .describe("Steps to execute sequentially"),
69
+ },
70
+ async ({ name, description, target, steps }) => {
71
+ const res = await unifiedFetch("/api/test/trigger/scenarios", "POST", {
72
+ name, description, target, steps,
73
+ });
74
+ const data = await res.json();
75
+ return {
76
+ content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
77
+ };
78
+ }
79
+ );
80
+
81
+ server.tool(
82
+ "test_save_scenario",
83
+ "Save a scenario to the library for later reuse",
84
+ {
85
+ name: z.string().describe("Scenario name"),
86
+ description: z.string().optional().describe("Scenario description"),
87
+ target: z
88
+ .string()
89
+ .describe(
90
+ "Target identifier — can be an absolute path (matches devtools.js?target= param) or a simple app name (e.g. 'kanban', 'dashboard')"
91
+ ),
92
+ steps: z
93
+ .array(
94
+ z.object({
95
+ command: z.string().describe("Command name"),
96
+ selector: z.string().optional(),
97
+ text: z.string().optional(),
98
+ value: z.string().optional(),
99
+ timeout: z.number().optional(),
100
+ ms: z.number().optional(),
101
+ clear: z.boolean().optional(),
102
+ contains: z.boolean().optional(),
103
+ path: z.string().optional(),
104
+ })
105
+ )
106
+ .describe("Steps to execute sequentially"),
107
+ },
108
+ async ({ name, description, target, steps }) => {
109
+ const saved = await ScenarioStore.getInstance().save({ name, description, target, steps });
110
+ return {
111
+ content: [{ type: "text" as const, text: JSON.stringify(saved, null, 2) }],
112
+ };
113
+ }
114
+ );
115
+
116
+ server.tool(
117
+ "test_list_saved",
118
+ "List saved scenarios for a specific target app or path",
119
+ {
120
+ target: z.string().describe(
121
+ "Target to filter by — app name (e.g. 'devglide', 'kanban') or absolute path. Required."
122
+ ),
123
+ },
124
+ async ({ target }) => {
125
+ const scenarios = await ScenarioStore.getInstance().list(target);
126
+ return {
127
+ content: [{ type: "text" as const, text: JSON.stringify(scenarios, null, 2) }],
128
+ };
129
+ }
130
+ );
131
+
132
+ server.tool(
133
+ "test_run_saved",
134
+ "Run a saved scenario by ID",
135
+ {
136
+ id: z.string().describe("Saved scenario ID"),
137
+ },
138
+ async ({ id }) => {
139
+ const res = await unifiedFetch(`/api/test/trigger/scenarios/saved/${id}/run`, "POST");
140
+ if (res.status === 404) {
141
+ return {
142
+ content: [{ type: "text" as const, text: `Scenario not found: ${id}` }],
143
+ };
144
+ }
145
+ const data = await res.json();
146
+ return {
147
+ content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
148
+ };
149
+ }
150
+ );
151
+
152
+ server.tool(
153
+ "test_delete_saved",
154
+ "Delete a saved scenario from the library",
155
+ {
156
+ id: z.string().describe("Saved scenario ID"),
157
+ },
158
+ async ({ id }) => {
159
+ const deleted = await ScenarioStore.getInstance().delete(id);
160
+ return {
161
+ content: [
162
+ {
163
+ type: "text" as const,
164
+ text: deleted ? `Scenario ${id} deleted` : `Scenario not found: ${id}`,
165
+ },
166
+ ],
167
+ };
168
+ }
169
+ );
170
+
171
+ server.tool(
172
+ "test_get_result",
173
+ "Get the execution result of a scenario by ID. Returns status (passed/failed), failed step index, error message, and duration.",
174
+ {
175
+ id: z.string().describe("Scenario ID to fetch the result for"),
176
+ },
177
+ async ({ id }) => {
178
+ const res = await unifiedFetch(`/api/test/trigger/scenarios/${id}/result`);
179
+ if (res.status === 404) {
180
+ return {
181
+ content: [{ type: "text" as const, text: `No result found for scenario: ${id}` }],
182
+ };
183
+ }
184
+ const data = await res.json();
185
+ return {
186
+ content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
187
+ };
188
+ }
189
+ );
190
+
191
+ return server;
192
+ }