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,108 @@
1
+ import { getProvider } from "../providers/index.js";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
3
+ import { join } from "path";
4
+ import { VOICE_DIR } from "../../../../packages/paths.js";
5
+
6
+ const DATA_DIR = VOICE_DIR;
7
+ const STATS_FILE = join(DATA_DIR, "stats.json");
8
+
9
+ export interface VoiceStats {
10
+ provider: string;
11
+ totalTranscriptions: number;
12
+ totalDurationSec: number;
13
+ totalErrors: number;
14
+ lastTranscriptionAt: string | null;
15
+ providerStatus: "configured" | "not_configured";
16
+ }
17
+
18
+ interface PersistedStats {
19
+ totalTranscriptions: number;
20
+ totalDurationSec: number;
21
+ totalErrors: number;
22
+ lastTranscriptionAt: string | null;
23
+ }
24
+
25
+ function loadStats(): PersistedStats {
26
+ try {
27
+ if (existsSync(STATS_FILE)) {
28
+ return JSON.parse(readFileSync(STATS_FILE, "utf-8"));
29
+ }
30
+ } catch {}
31
+ return { totalTranscriptions: 0, totalDurationSec: 0, totalErrors: 0, lastTranscriptionAt: null };
32
+ }
33
+
34
+ function saveStats(data: PersistedStats): void {
35
+ mkdirSync(DATA_DIR, { recursive: true });
36
+ writeFileSync(STATS_FILE, JSON.stringify(data, null, 2));
37
+ }
38
+
39
+ class StatsTracker {
40
+ private static instance: StatsTracker;
41
+
42
+ private totalTranscriptions: number;
43
+ private totalDurationSec: number;
44
+ private totalErrors: number;
45
+ private lastTranscriptionAt: Date | null;
46
+
47
+ private constructor() {
48
+ const persisted = loadStats();
49
+ this.totalTranscriptions = persisted.totalTranscriptions;
50
+ this.totalDurationSec = persisted.totalDurationSec;
51
+ this.totalErrors = persisted.totalErrors;
52
+ this.lastTranscriptionAt = persisted.lastTranscriptionAt ? new Date(persisted.lastTranscriptionAt) : null;
53
+ }
54
+
55
+ static getInstance(): StatsTracker {
56
+ if (!StatsTracker.instance) {
57
+ StatsTracker.instance = new StatsTracker();
58
+ }
59
+ return StatsTracker.instance;
60
+ }
61
+
62
+ private flush(): void {
63
+ saveStats({
64
+ totalTranscriptions: this.totalTranscriptions,
65
+ totalDurationSec: this.totalDurationSec,
66
+ totalErrors: this.totalErrors,
67
+ lastTranscriptionAt: this.lastTranscriptionAt?.toISOString() ?? null,
68
+ });
69
+ }
70
+
71
+ recordSuccess(durationSec?: number) {
72
+ this.totalTranscriptions++;
73
+ if (durationSec) {
74
+ this.totalDurationSec += durationSec;
75
+ }
76
+ this.lastTranscriptionAt = new Date();
77
+ this.flush();
78
+ }
79
+
80
+ recordError() {
81
+ this.totalErrors++;
82
+ this.flush();
83
+ }
84
+
85
+ reset() {
86
+ this.totalTranscriptions = 0;
87
+ this.totalDurationSec = 0;
88
+ this.totalErrors = 0;
89
+ this.lastTranscriptionAt = null;
90
+ this.flush();
91
+ }
92
+
93
+ getStats(): VoiceStats {
94
+ const provider = getProvider();
95
+ return {
96
+ provider: provider.name,
97
+ totalTranscriptions: this.totalTranscriptions,
98
+ totalDurationSec: Math.round(this.totalDurationSec * 100) / 100,
99
+ totalErrors: this.totalErrors,
100
+ lastTranscriptionAt: this.lastTranscriptionAt?.toISOString() ?? null,
101
+ providerStatus: provider.isConfigured()
102
+ ? "configured"
103
+ : "not_configured",
104
+ };
105
+ }
106
+ }
107
+
108
+ export const stats = StatsTracker.getInstance();
@@ -0,0 +1,11 @@
1
+ import { getProvider } from "./providers/index.js";
2
+ import type { TranscribeOptions, TranscriptionResult } from "./providers/types.js";
3
+
4
+ export type { TranscribeOptions, TranscriptionResult };
5
+
6
+ export async function transcribe(
7
+ audio: File,
8
+ options?: TranscribeOptions
9
+ ): Promise<TranscriptionResult> {
10
+ return getProvider().transcribe(audio, options);
11
+ }
@@ -0,0 +1,16 @@
1
+ /** Canonical extension-to-MIME mapping for audio files. */
2
+ const mimeMap: Record<string, string> = {
3
+ ogg: "audio/ogg",
4
+ mp4: "audio/mp4",
5
+ m4a: "audio/mp4",
6
+ mp3: "audio/mpeg",
7
+ wav: "audio/wav",
8
+ flac: "audio/flac",
9
+ webm: "audio/webm",
10
+ };
11
+
12
+ /** Return the audio MIME type for a filename, defaulting to `audio/webm`. */
13
+ export function mimeFromFilename(filename: string): string {
14
+ const ext = filename.split(".").pop()?.toLowerCase();
15
+ return mimeMap[ext || ""] || "audio/webm";
16
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../packages/tsconfig/node.json",
3
+ "compilerOptions": {
4
+ "noEmit": true
5
+ },
6
+ "include": ["src"],
7
+ "exclude": ["node_modules", "dist"]
8
+ }
@@ -0,0 +1,5 @@
1
+  WARN  Issue while reading "/home/runner/_work/devglide/devglide/.npmrc". Failed to replace env in config: ${NODE_AUTH_TOKEN}
2
+
3
+ > @devglide/workflow@0.1.0 build /home/runner/_work/devglide/devglide/src/apps/workflow
4
+ > tsc
5
+
@@ -0,0 +1,96 @@
1
+  WARN  Issue while reading "/home/runner/_work/devglide/devglide/.npmrc". Failed to replace env in config: ${NODE_AUTH_TOKEN}
2
+
3
+ > @devglide/workflow@0.1.0 lint /home/runner/_work/devglide/devglide/src/apps/workflow
4
+ > eslint .
5
+
6
+
7
+ /home/runner/_work/devglide/devglide/src/apps/workflow/engine/executors/file-executor.ts
8
+ 2:1 warning There should be at least one empty line between import groups import/order
9
+
10
+ /home/runner/_work/devglide/devglide/src/apps/workflow/engine/executors/git-executor.ts
11
+ 1:1 warning There should be at least one empty line between import groups import/order
12
+
13
+ /home/runner/_work/devglide/devglide/src/apps/workflow/engine/executors/index.ts
14
+ 1:1 warning There should be at least one empty line between import groups import/order
15
+ 2:1 warning `./trigger-executor.js` import should occur after import of `./sub-workflow-executor.js` import/order
16
+ 3:1 warning `./shell-executor.js` import should occur after import of `./loop-executor.js` import/order
17
+ 4:1 warning `./kanban-executor.js` import should occur after import of `./decision-executor.js` import/order
18
+ 5:1 warning `./git-executor.js` import should occur after import of `./decision-executor.js` import/order
19
+ 6:1 warning `./test-executor.js` import should occur after import of `./sub-workflow-executor.js` import/order
20
+ 7:1 warning `./log-executor.js` import should occur after import of `./decision-executor.js` import/order
21
+ 8:1 warning `./file-executor.js` import should occur after import of `./decision-executor.js` import/order
22
+ 9:1 warning `./llm-executor.js` import should occur after import of `./decision-executor.js` import/order
23
+ 10:1 warning `./http-executor.js` import should occur after import of `./decision-executor.js` import/order
24
+
25
+ /home/runner/_work/devglide/devglide/src/apps/workflow/engine/executors/kanban-executor.ts
26
+ 1:1 warning There should be at least one empty line between import groups import/order
27
+ 1:1 warning `../../types.js` type import should occur after import of `../../../../apps/kanban/src/db.js` import/order
28
+ 2:1 warning There should be at least one empty line between import groups import/order
29
+
30
+ /home/runner/_work/devglide/devglide/src/apps/workflow/engine/executors/llm-executor.ts
31
+ 2:1 warning There should be at least one empty line between import groups import/order
32
+ 3:1 warning There should be at least one empty line between import groups import/order
33
+
34
+ /home/runner/_work/devglide/devglide/src/apps/workflow/engine/executors/log-executor.ts
35
+ 2:1 warning There should be at least one empty line between import groups import/order
36
+ 4:1 warning `../../../../apps/log/src/services/log-writer.js` import should occur before type import of `../../types.js` import/order
37
+
38
+ /home/runner/_work/devglide/devglide/src/apps/workflow/engine/executors/shell-executor.ts
39
+ 2:1 warning There should be at least one empty line between import groups import/order
40
+
41
+ /home/runner/_work/devglide/devglide/src/apps/workflow/engine/expression-evaluator.ts
42
+ 1:1 warning There should be at least one empty line between import groups import/order
43
+
44
+ /home/runner/_work/devglide/devglide/src/apps/workflow/engine/graph-runner.ts
45
+ 1:1 warning There should be at least one empty line between import groups import/order
46
+ 8:3 warning 'RunStatus' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
47
+ 12:1 warning There should be at least one empty line between import groups import/order
48
+ 12:1 warning `./expression-evaluator.js` import should occur before import of `./node-executor.js` import/order
49
+ 13:1 warning `../../../project-context.js` import should occur before type import of `../types.js` import/order
50
+
51
+ /home/runner/_work/devglide/devglide/src/apps/workflow/engine/node-executor.ts
52
+ 1:1 warning There should be at least one empty line between import groups import/order
53
+
54
+ /home/runner/_work/devglide/devglide/src/apps/workflow/mcp.ts
55
+ 2:1 warning There should be at least one empty line between import groups import/order
56
+ 3:1 warning There should be at least one empty line between import groups import/order
57
+ 4:1 warning There should be at least one empty line between import groups import/order
58
+ 4:1 warning `../../project-context.js` import should occur before import of `./services/workflow-store.js` import/order
59
+ 5:1 warning There should be at least one empty line between import groups import/order
60
+ 5:15 warning 'Workflow' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
61
+ 6:1 warning `../../packages/mcp-utils/src/index.js` import should occur before import of `./services/workflow-store.js` import/order
62
+
63
+ /home/runner/_work/devglide/devglide/src/apps/workflow/services/custom-node-loader.ts
64
+ 2:1 warning There should be at least one empty line between import groups import/order
65
+
66
+ /home/runner/_work/devglide/devglide/src/apps/workflow/services/legacy-converter.ts
67
+ 1:1 warning There should be at least one empty line between import groups import/order
68
+
69
+ /home/runner/_work/devglide/devglide/src/apps/workflow/services/run-manager.ts
70
+ 1:1 warning There should be at least one empty line between import groups import/order
71
+ 2:1 warning There should be at least one empty line between import groups import/order
72
+ 4:1 warning `../engine/graph-runner.js` import should occur before type import of `../types.js` import/order
73
+
74
+ /home/runner/_work/devglide/devglide/src/apps/workflow/services/workflow-store.ts
75
+ 2:1 warning There should be at least one empty line between import groups import/order
76
+ 3:1 warning There should be at least one empty line between import groups import/order
77
+ 5:1 warning `../../../project-context.js` import should occur before type import of `../types.js` import/order
78
+ 6:1 warning `../../../packages/paths.js` import should occur before type import of `../types.js` import/order
79
+ 7:1 warning `../../../packages/json-file-store.js` import should occur before type import of `../types.js` import/order
80
+ 279:37 warning Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
81
+ 389:37 warning Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
82
+
83
+ /home/runner/_work/devglide/devglide/src/apps/workflow/services/workflow-validator.test.ts
84
+ 1:1 warning There should be at least one empty line between import groups import/order
85
+
86
+ /home/runner/_work/devglide/devglide/src/apps/workflow/services/workflow-validator.ts
87
+ 2:1 warning `../engine/node-registry.js` import should occur before type import of `../types.js` import/order
88
+ 49:37 warning Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
89
+
90
+ /home/runner/_work/devglide/devglide/src/apps/workflow/src/index.ts
91
+ 2:1 warning There should be at least one empty line between import groups import/order
92
+ 3:1 warning `@devglide/mcp-utils` import should occur before import of `../mcp.js` import/order
93
+
94
+ ✖ 51 problems (0 errors, 51 warnings)
95
+ 0 errors and 46 warnings potentially fixable with the `--fix` option.
96
+
@@ -0,0 +1,5 @@
1
+  WARN  Issue while reading "/home/runner/_work/devglide/devglide/.npmrc". Failed to replace env in config: ${NODE_AUTH_TOKEN}
2
+
3
+ > @devglide/workflow@0.1.0 typecheck /home/runner/_work/devglide/devglide/src/apps/workflow
4
+ > tsc --noEmit
5
+
@@ -0,0 +1,87 @@
1
+ import type { ExecutorFunction, ExecutorResult, NodeConfig, ExecutionContext, SSEEmitter, DecisionConfig } from '../../types.js';
2
+ import { evaluate } from '../expression-evaluator.js';
3
+
4
+ /**
5
+ * Decision executor evaluates conditions and sets __decision_port.
6
+ * The graph-runner's handleDecision() reads __decision_port to route edges —
7
+ * this is the single source of truth for decision routing.
8
+ */
9
+ export const decisionExecutor: ExecutorFunction = async (
10
+ config: NodeConfig,
11
+ context: ExecutionContext,
12
+ _emit: SSEEmitter,
13
+ ): Promise<ExecutorResult> => {
14
+ const cfg = config as DecisionConfig;
15
+
16
+ try {
17
+ let selectedPort: string | undefined;
18
+
19
+ switch (cfg.conditionType) {
20
+ case 'exit-code': {
21
+ // Use actual predecessors (set by graph-runner before execution)
22
+ // rather than sorting all node states globally
23
+ const predecessorIds = context.variables.get('__predecessor_ids') as string[] | undefined;
24
+ let exitCode: number | undefined;
25
+
26
+ if (predecessorIds && predecessorIds.length > 0) {
27
+ // Use the most recently completed predecessor
28
+ const predecessorStates = predecessorIds
29
+ .map((id) => context.nodeStates.get(id))
30
+ .filter((s) => s?.completedAt)
31
+ .sort((a, b) => (b!.completedAt ?? '').localeCompare(a!.completedAt ?? ''));
32
+ exitCode = predecessorStates[0]?.exitCode;
33
+ }
34
+
35
+ const code = exitCode ?? -1;
36
+ for (const port of cfg.ports) {
37
+ if (port.condition && String(code) === port.condition) {
38
+ selectedPort = port.id;
39
+ break;
40
+ }
41
+ }
42
+ break;
43
+ }
44
+
45
+ case 'variable': {
46
+ if (!cfg.variable) {
47
+ return { status: 'failed', error: 'variable is required for variable condition type' };
48
+ }
49
+ const value = String(context.variables.get(cfg.variable) ?? '');
50
+
51
+ for (const port of cfg.ports) {
52
+ if (port.condition && value === port.condition) {
53
+ selectedPort = port.id;
54
+ break;
55
+ }
56
+ }
57
+ break;
58
+ }
59
+
60
+ case 'expression': {
61
+ for (const port of cfg.ports) {
62
+ if (port.condition) {
63
+ const match = evaluate(port.condition, context);
64
+ if (match) {
65
+ selectedPort = port.id;
66
+ break;
67
+ }
68
+ }
69
+ }
70
+ break;
71
+ }
72
+ }
73
+
74
+ if (!selectedPort && cfg.ports.length > 0) {
75
+ const defaultPort = cfg.ports.find((p) => !p.condition) ?? cfg.ports[cfg.ports.length - 1];
76
+ selectedPort = defaultPort.id;
77
+ }
78
+
79
+ return {
80
+ status: 'passed',
81
+ output: selectedPort,
82
+ variables: { __decision_port: selectedPort },
83
+ };
84
+ } catch (err) {
85
+ return { status: 'failed', error: (err as Error).message };
86
+ }
87
+ };
@@ -0,0 +1,90 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import type { ExecutorFunction, ExecutorResult, NodeConfig, ExecutionContext, SSEEmitter, FileConfig } from '../../types.js';
4
+
5
+ async function safePath(reqPath: string, root: string): Promise<string> {
6
+ const abs = path.resolve(root, reqPath.replace(/^\/+/, ''));
7
+ if (!abs.startsWith(root + path.sep) && abs !== root) throw new Error('Path traversal denied');
8
+
9
+ // Resolve symlinks to prevent symlink-based traversal.
10
+ // For non-existing targets (write/append), walk up to the nearest existing ancestor.
11
+ const realRoot = await fs.realpath(root);
12
+ let check = abs;
13
+ while (true) {
14
+ try {
15
+ const real = await fs.realpath(check);
16
+ if (!real.startsWith(realRoot + path.sep) && real !== realRoot) {
17
+ throw new Error('Symlink traversal denied');
18
+ }
19
+ break;
20
+ } catch (err: unknown) {
21
+ if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;
22
+ const parent = path.dirname(check);
23
+ if (parent === check) break; // hit filesystem root
24
+ check = parent;
25
+ }
26
+ }
27
+
28
+ return abs;
29
+ }
30
+
31
+ export const fileExecutor: ExecutorFunction = async (
32
+ config: NodeConfig,
33
+ _context: ExecutionContext,
34
+ _emit: SSEEmitter,
35
+ ): Promise<ExecutorResult> => {
36
+ const cfg = config as FileConfig;
37
+ const root = _context.project?.path ?? process.cwd();
38
+
39
+ try {
40
+ const target = await safePath(cfg.path, root);
41
+
42
+ switch (cfg.operation) {
43
+ case 'read': {
44
+ const content = await fs.readFile(target, 'utf-8');
45
+ return { status: 'passed', output: content };
46
+ }
47
+
48
+ case 'write': {
49
+ if (cfg.content === undefined) {
50
+ return { status: 'failed', error: 'content is required for write' };
51
+ }
52
+ await fs.mkdir(path.dirname(target), { recursive: true });
53
+ await fs.writeFile(target, cfg.content, 'utf-8');
54
+ return { status: 'passed', output: `Written to ${target}` };
55
+ }
56
+
57
+ case 'append': {
58
+ if (cfg.content === undefined) {
59
+ return { status: 'failed', error: 'content is required for append' };
60
+ }
61
+ await fs.mkdir(path.dirname(target), { recursive: true });
62
+ await fs.appendFile(target, cfg.content, 'utf-8');
63
+ return { status: 'passed', output: `Appended to ${target}` };
64
+ }
65
+
66
+ case 'exists': {
67
+ try {
68
+ await fs.access(target);
69
+ return { status: 'passed', output: true };
70
+ } catch {
71
+ return { status: 'passed', output: false };
72
+ }
73
+ }
74
+
75
+ case 'tree': {
76
+ const entries = await fs.readdir(target, { withFileTypes: true });
77
+ const listing = entries.map((e) => ({
78
+ name: e.name,
79
+ type: e.isDirectory() ? 'directory' : 'file',
80
+ }));
81
+ return { status: 'passed', output: listing };
82
+ }
83
+
84
+ default:
85
+ return { status: 'failed', error: `Unknown file operation: ${(cfg as FileConfig).operation}` };
86
+ }
87
+ } catch (err) {
88
+ return { status: 'failed', error: (err as Error).message };
89
+ }
90
+ };
@@ -0,0 +1,137 @@
1
+ import { spawn } from 'child_process';
2
+ import type { ExecutorFunction, ExecutorResult, NodeConfig, ExecutionContext, SSEEmitter, GitConfig } from '../../types.js';
3
+
4
+ const GIT_TIMEOUT_MS = 60_000; // 1 minute
5
+ const BRANCH_RE = /^[a-zA-Z0-9._/\-]+$/;
6
+
7
+ function runGit(args: string[], cwd: string): Promise<{ output: string; exitCode: number }> {
8
+ return new Promise((resolve, reject) => {
9
+ const ac = new AbortController();
10
+ const child = spawn('git', args, { cwd, signal: ac.signal });
11
+ const timer = setTimeout(() => ac.abort(), GIT_TIMEOUT_MS);
12
+ let output = '';
13
+
14
+ child.stdout?.on('data', (data: Buffer) => { output += data.toString(); });
15
+ child.stderr?.on('data', (data: Buffer) => { output += data.toString(); });
16
+ child.on('error', (err) => {
17
+ clearTimeout(timer);
18
+ if (err.name === 'AbortError') {
19
+ resolve({ output: output.trim() + '\n[TIMEOUT]', exitCode: 124 });
20
+ } else {
21
+ reject(err);
22
+ }
23
+ });
24
+ child.on('close', (code) => {
25
+ clearTimeout(timer);
26
+ resolve({ output: output.trim(), exitCode: code ?? 1 });
27
+ });
28
+ });
29
+ }
30
+
31
+ /** Validate branch name — reject flag-like values and invalid characters. */
32
+ function safeBranch(name: string): string | null {
33
+ if (!name || name.startsWith('-') || !BRANCH_RE.test(name)) return null;
34
+ return name;
35
+ }
36
+
37
+ /** Sanitize file paths — reject flag-like values. */
38
+ function safeFiles(files: string[]): string[] | null {
39
+ for (const f of files) {
40
+ if (f.startsWith('-')) return null;
41
+ }
42
+ return files;
43
+ }
44
+
45
+ export const gitExecutor: ExecutorFunction = async (
46
+ config: NodeConfig,
47
+ _context: ExecutionContext,
48
+ emit: SSEEmitter,
49
+ ): Promise<ExecutorResult> => {
50
+ const cfg = config as GitConfig;
51
+ const cwd = _context.project?.path ?? process.cwd();
52
+
53
+ try {
54
+ let result: { output: string; exitCode: number };
55
+
56
+ switch (cfg.operation) {
57
+ case 'status':
58
+ result = await runGit(['status', '--porcelain'], cwd);
59
+ break;
60
+
61
+ case 'diff':
62
+ result = await runGit(['diff'], cwd);
63
+ break;
64
+
65
+ case 'commit': {
66
+ if (!cfg.message) {
67
+ return { status: 'failed', error: 'message is required for commit' };
68
+ }
69
+ let addArgs: string[];
70
+ if (cfg.files && cfg.files.length > 0) {
71
+ const safe = safeFiles(cfg.files);
72
+ if (!safe) return { status: 'failed', error: 'Invalid file path — must not start with -' };
73
+ addArgs = ['add', '--', ...safe];
74
+ } else {
75
+ addArgs = ['add', '-A'];
76
+ }
77
+ const addResult = await runGit(addArgs, cwd);
78
+ if (addResult.exitCode !== 0) {
79
+ return { status: 'failed', output: addResult.output, exitCode: addResult.exitCode, error: addResult.output };
80
+ }
81
+ result = await runGit(['commit', '-m', cfg.message], cwd);
82
+ break;
83
+ }
84
+
85
+ case 'push':
86
+ result = await runGit(['push'], cwd);
87
+ break;
88
+
89
+ case 'branch-create': {
90
+ if (!cfg.branch) {
91
+ return { status: 'failed', error: 'branch is required for branch-create' };
92
+ }
93
+ const newBranch = safeBranch(cfg.branch);
94
+ if (!newBranch) return { status: 'failed', error: 'Invalid branch name' };
95
+ result = await runGit(['checkout', '-b', '--', newBranch], cwd);
96
+ break;
97
+ }
98
+
99
+ case 'checkout': {
100
+ if (!cfg.branch) {
101
+ return { status: 'failed', error: 'branch is required for checkout' };
102
+ }
103
+ const target = safeBranch(cfg.branch);
104
+ if (!target) return { status: 'failed', error: 'Invalid branch name' };
105
+ result = await runGit(['checkout', '--', target], cwd);
106
+ break;
107
+ }
108
+
109
+ case 'add': {
110
+ let addArgs: string[];
111
+ if (cfg.files && cfg.files.length > 0) {
112
+ const safe = safeFiles(cfg.files);
113
+ if (!safe) return { status: 'failed', error: 'Invalid file path — must not start with -' };
114
+ addArgs = ['add', '--', ...safe];
115
+ } else {
116
+ addArgs = ['add', '-A'];
117
+ }
118
+ result = await runGit(addArgs, cwd);
119
+ break;
120
+ }
121
+
122
+ default:
123
+ return { status: 'failed', error: `Unknown git operation: ${(cfg as GitConfig).operation}` };
124
+ }
125
+
126
+ emit({ type: 'output', nodeId: _context.runId, data: result.output });
127
+
128
+ return {
129
+ status: result.exitCode === 0 ? 'passed' : 'failed',
130
+ output: result.output,
131
+ exitCode: result.exitCode,
132
+ error: result.exitCode !== 0 ? result.output : undefined,
133
+ };
134
+ } catch (err) {
135
+ return { status: 'failed', error: (err as Error).message };
136
+ }
137
+ };
@@ -0,0 +1,65 @@
1
+ import type { ExecutorFunction, ExecutorResult, NodeConfig, ExecutionContext, SSEEmitter, HttpConfig } from '../../types.js';
2
+
3
+ const BLOCKED_HOSTS = new Set([
4
+ 'localhost', '127.0.0.1', '[::1]', '::1', '0.0.0.0',
5
+ 'metadata.google.internal', '169.254.169.254',
6
+ ]);
7
+
8
+ function isBlockedUrl(urlStr: string): string | null {
9
+ let parsed: URL;
10
+ try { parsed = new URL(urlStr); } catch { return 'Invalid URL'; }
11
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') return 'Only HTTP/HTTPS allowed';
12
+ const hostname = parsed.hostname.toLowerCase();
13
+ if (BLOCKED_HOSTS.has(hostname)) return `Blocked host: ${hostname}`;
14
+ const parts = hostname.split('.').map(Number);
15
+ if (parts.length === 4 && parts.every((n) => !isNaN(n))) {
16
+ const [a, b] = parts;
17
+ if (a === 10) return 'Private IP blocked';
18
+ if (a === 172 && b >= 16 && b <= 31) return 'Private IP blocked';
19
+ if (a === 192 && b === 168) return 'Private IP blocked';
20
+ if (a === 169 && b === 254) return 'Link-local IP blocked';
21
+ if (a === 127) return 'Loopback IP blocked';
22
+ }
23
+ return null;
24
+ }
25
+
26
+ export const httpExecutor: ExecutorFunction = async (
27
+ config: NodeConfig,
28
+ _context: ExecutionContext,
29
+ _emit: SSEEmitter,
30
+ ): Promise<ExecutorResult> => {
31
+ const cfg = config as HttpConfig;
32
+
33
+ try {
34
+ if (!cfg.url) {
35
+ return { status: 'failed', error: 'url is required' };
36
+ }
37
+
38
+ const blocked = isBlockedUrl(cfg.url);
39
+ if (blocked) {
40
+ return { status: 'failed', error: `SSRF blocked: ${blocked}` };
41
+ }
42
+
43
+ const init: RequestInit = {
44
+ method: cfg.method,
45
+ headers: cfg.headers,
46
+ };
47
+
48
+ if (cfg.body && cfg.method !== 'GET') {
49
+ init.body = cfg.body;
50
+ }
51
+
52
+ const response = await fetch(cfg.url, init);
53
+ const body = await response.text();
54
+ const ok = response.status >= 200 && response.status < 300;
55
+
56
+ return {
57
+ status: ok ? 'passed' : 'failed',
58
+ output: body,
59
+ exitCode: response.status,
60
+ error: ok ? undefined : `HTTP ${response.status}: ${response.statusText}`,
61
+ };
62
+ } catch (err) {
63
+ return { status: 'failed', error: (err as Error).message };
64
+ }
65
+ };
@@ -0,0 +1,28 @@
1
+ import { registerExecutor } from '../node-registry.js';
2
+ import { triggerExecutor } from './trigger-executor.js';
3
+ import { shellExecutor } from './shell-executor.js';
4
+ import { kanbanExecutor } from './kanban-executor.js';
5
+ import { gitExecutor } from './git-executor.js';
6
+ import { testExecutor } from './test-executor.js';
7
+ import { logExecutor } from './log-executor.js';
8
+ import { fileExecutor } from './file-executor.js';
9
+ import { llmExecutor } from './llm-executor.js';
10
+ import { httpExecutor } from './http-executor.js';
11
+ import { decisionExecutor } from './decision-executor.js';
12
+ import { loopExecutor } from './loop-executor.js';
13
+ import { subWorkflowExecutor } from './sub-workflow-executor.js';
14
+
15
+ export function registerAllExecutors(): void {
16
+ registerExecutor('trigger', triggerExecutor);
17
+ registerExecutor('action:shell', shellExecutor);
18
+ registerExecutor('action:kanban', kanbanExecutor);
19
+ registerExecutor('action:git', gitExecutor);
20
+ registerExecutor('action:test', testExecutor);
21
+ registerExecutor('action:log', logExecutor);
22
+ registerExecutor('action:file', fileExecutor);
23
+ registerExecutor('action:llm', llmExecutor);
24
+ registerExecutor('action:http', httpExecutor);
25
+ registerExecutor('decision', decisionExecutor);
26
+ registerExecutor('loop', loopExecutor);
27
+ registerExecutor('sub-workflow', subWorkflowExecutor);
28
+ }