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.
- package/LICENSE +21 -0
- package/README.md +338 -0
- package/bin/claude-md-template.js +94 -0
- package/bin/devglide.js +387 -0
- package/package.json +85 -0
- package/pnpm-workspace.yaml +3 -0
- package/src/apps/coder/.turbo/turbo-lint.log +5 -0
- package/src/apps/coder/package.json +16 -0
- package/src/apps/coder/public/favicon.svg +7 -0
- package/src/apps/coder/public/page.css +275 -0
- package/src/apps/coder/public/page.js +528 -0
- package/src/apps/coder/server.js +3 -0
- package/src/apps/documentation/public/page.css +597 -0
- package/src/apps/documentation/public/page.js +609 -0
- package/src/apps/kanban/.turbo/turbo-lint.log +97 -0
- package/src/apps/kanban/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/kanban/package.json +32 -0
- package/src/apps/kanban/public/favicon.svg +7 -0
- package/src/apps/kanban/public/page.css +1010 -0
- package/src/apps/kanban/public/page.js +1730 -0
- package/src/apps/kanban/public/vendor/marked.min.js +6 -0
- package/src/apps/kanban/public/vendor/sortable.min.js +2 -0
- package/src/apps/kanban/src/db.ts +319 -0
- package/src/apps/kanban/src/index.ts +14 -0
- package/src/apps/kanban/src/mcp-helpers.test.ts +88 -0
- package/src/apps/kanban/src/mcp-helpers.ts +60 -0
- package/src/apps/kanban/src/mcp.ts +59 -0
- package/src/apps/kanban/src/routes/attachments.ts +161 -0
- package/src/apps/kanban/src/routes/features.ts +233 -0
- package/src/apps/kanban/src/routes/issues.ts +373 -0
- package/src/apps/kanban/src/tools/feature-tools.ts +164 -0
- package/src/apps/kanban/src/tools/item-tools.ts +307 -0
- package/src/apps/kanban/src/tools/versioned-entry-tools.ts +72 -0
- package/src/apps/kanban/tsconfig.check.json +9 -0
- package/src/apps/kanban/tsconfig.json +9 -0
- package/src/apps/keymap/.turbo/turbo-lint.log +5 -0
- package/src/apps/keymap/package.json +16 -0
- package/src/apps/keymap/public/page.css +275 -0
- package/src/apps/keymap/public/page.js +294 -0
- package/src/apps/keymap/server.js +25 -0
- package/src/apps/log/.turbo/turbo-build.log +5 -0
- package/src/apps/log/.turbo/turbo-lint.log +45 -0
- package/src/apps/log/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/log/node_modules/.bin/tsc +21 -0
- package/src/apps/log/node_modules/.bin/tsserver +21 -0
- package/src/apps/log/node_modules/.bin/tsx +21 -0
- package/src/apps/log/package.json +36 -0
- package/src/apps/log/public/console-sniffer.js +221 -0
- package/src/apps/log/public/favicon.svg +7 -0
- package/src/apps/log/public/page.css +322 -0
- package/src/apps/log/public/page.js +463 -0
- package/src/apps/log/src/index.ts +9 -0
- package/src/apps/log/src/mcp.ts +122 -0
- package/src/apps/log/src/routes/log.ts +333 -0
- package/src/apps/log/src/routes/status.ts +25 -0
- package/src/apps/log/src/server-sniffer.ts +118 -0
- package/src/apps/log/src/services/file-patterns.ts +39 -0
- package/src/apps/log/src/services/file-tailer.ts +228 -0
- package/src/apps/log/src/services/line-parser.ts +94 -0
- package/src/apps/log/src/services/log-writer.ts +39 -0
- package/src/apps/log/tsconfig.json +8 -0
- package/src/apps/prompts/.turbo/turbo-build.log +5 -0
- package/src/apps/prompts/.turbo/turbo-lint.log +24 -0
- package/src/apps/prompts/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/prompts/mcp.ts +175 -0
- package/src/apps/prompts/node_modules/.bin/tsc +21 -0
- package/src/apps/prompts/node_modules/.bin/tsserver +21 -0
- package/src/apps/prompts/node_modules/.bin/tsx +21 -0
- package/src/apps/prompts/package.json +25 -0
- package/src/apps/prompts/public/page.css +315 -0
- package/src/apps/prompts/public/page.js +541 -0
- package/src/apps/prompts/services/prompt-store.ts +212 -0
- package/src/apps/prompts/src/index.ts +9 -0
- package/src/apps/prompts/tsconfig.json +8 -0
- package/src/apps/prompts/types.ts +27 -0
- package/src/apps/shell/.turbo/turbo-build.log +5 -0
- package/src/apps/shell/.turbo/turbo-lint.log +34 -0
- package/src/apps/shell/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/shell/package.json +35 -0
- package/src/apps/shell/public/favicon.svg +7 -0
- package/src/apps/shell/public/page.css +407 -0
- package/src/apps/shell/public/page.js +1577 -0
- package/src/apps/shell/src/index.ts +150 -0
- package/src/apps/shell/src/mcp.ts +398 -0
- package/src/apps/shell/src/shell-types.ts +41 -0
- package/src/apps/shell/tsconfig.json +8 -0
- package/src/apps/test/.turbo/turbo-build.log +5 -0
- package/src/apps/test/.turbo/turbo-lint.log +27 -0
- package/src/apps/test/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/test/node_modules/.bin/tsc +21 -0
- package/src/apps/test/node_modules/.bin/tsserver +21 -0
- package/src/apps/test/node_modules/.bin/tsx +21 -0
- package/src/apps/test/node_modules/.bin/uuid +21 -0
- package/src/apps/test/package.json +35 -0
- package/src/apps/test/public/favicon.svg +7 -0
- package/src/apps/test/public/page.css +499 -0
- package/src/apps/test/public/page.js +417 -0
- package/src/apps/test/public/scenario-runner.js +450 -0
- package/src/apps/test/src/index.ts +9 -0
- package/src/apps/test/src/mcp.ts +192 -0
- package/src/apps/test/src/routes/trigger.ts +285 -0
- package/src/apps/test/src/services/scenario-broadcaster.ts +60 -0
- package/src/apps/test/src/services/scenario-manager.ts +361 -0
- package/src/apps/test/src/services/scenario-store.ts +145 -0
- package/src/apps/test/tsconfig.json +8 -0
- package/src/apps/vocabulary/.turbo/turbo-build.log +5 -0
- package/src/apps/vocabulary/.turbo/turbo-lint.log +25 -0
- package/src/apps/vocabulary/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/vocabulary/mcp.ts +173 -0
- package/src/apps/vocabulary/node_modules/.bin/tsc +21 -0
- package/src/apps/vocabulary/node_modules/.bin/tsserver +21 -0
- package/src/apps/vocabulary/node_modules/.bin/tsx +21 -0
- package/src/apps/vocabulary/package.json +25 -0
- package/src/apps/vocabulary/public/page.css +247 -0
- package/src/apps/vocabulary/public/page.js +444 -0
- package/src/apps/vocabulary/services/vocabulary-store.ts +179 -0
- package/src/apps/vocabulary/src/index.ts +10 -0
- package/src/apps/vocabulary/tsconfig.json +8 -0
- package/src/apps/vocabulary/types.ts +22 -0
- package/src/apps/voice/.turbo/turbo-build.log +5 -0
- package/src/apps/voice/.turbo/turbo-lint.log +43 -0
- package/src/apps/voice/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/voice/node_modules/.bin/openai +21 -0
- package/src/apps/voice/node_modules/.bin/tsc +21 -0
- package/src/apps/voice/node_modules/.bin/tsserver +21 -0
- package/src/apps/voice/node_modules/.bin/tsx +21 -0
- package/src/apps/voice/package.json +35 -0
- package/src/apps/voice/public/favicon.svg +7 -0
- package/src/apps/voice/public/page.css +388 -0
- package/src/apps/voice/public/page.js +718 -0
- package/src/apps/voice/src/index.ts +10 -0
- package/src/apps/voice/src/mcp.ts +70 -0
- package/src/apps/voice/src/providers/index.ts +85 -0
- package/src/apps/voice/src/providers/openai-compatible.ts +94 -0
- package/src/apps/voice/src/providers/types.ts +27 -0
- package/src/apps/voice/src/routes/config.ts +118 -0
- package/src/apps/voice/src/routes/transcribe.ts +90 -0
- package/src/apps/voice/src/services/config-store.ts +129 -0
- package/src/apps/voice/src/services/stats.ts +108 -0
- package/src/apps/voice/src/transcribe.ts +11 -0
- package/src/apps/voice/src/utils/mime.ts +16 -0
- package/src/apps/voice/tsconfig.json +8 -0
- package/src/apps/workflow/.turbo/turbo-build.log +5 -0
- package/src/apps/workflow/.turbo/turbo-lint.log +96 -0
- package/src/apps/workflow/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/workflow/engine/executors/decision-executor.ts +87 -0
- package/src/apps/workflow/engine/executors/file-executor.ts +90 -0
- package/src/apps/workflow/engine/executors/git-executor.ts +137 -0
- package/src/apps/workflow/engine/executors/http-executor.ts +65 -0
- package/src/apps/workflow/engine/executors/index.ts +28 -0
- package/src/apps/workflow/engine/executors/kanban-executor.ts +154 -0
- package/src/apps/workflow/engine/executors/llm-executor.ts +46 -0
- package/src/apps/workflow/engine/executors/log-executor.ts +62 -0
- package/src/apps/workflow/engine/executors/loop-executor.ts +14 -0
- package/src/apps/workflow/engine/executors/shell-executor.ts +107 -0
- package/src/apps/workflow/engine/executors/sub-workflow-executor.ts +61 -0
- package/src/apps/workflow/engine/executors/test-executor.ts +73 -0
- package/src/apps/workflow/engine/executors/trigger-executor.ts +39 -0
- package/src/apps/workflow/engine/expression-evaluator.ts +117 -0
- package/src/apps/workflow/engine/graph-runner.ts +438 -0
- package/src/apps/workflow/engine/node-executor.ts +104 -0
- package/src/apps/workflow/engine/node-registry.ts +15 -0
- package/src/apps/workflow/engine/variable-resolver.ts +109 -0
- package/src/apps/workflow/mcp.ts +223 -0
- package/src/apps/workflow/node_modules/.bin/tsc +21 -0
- package/src/apps/workflow/node_modules/.bin/tsserver +21 -0
- package/src/apps/workflow/node_modules/.bin/tsx +21 -0
- package/src/apps/workflow/package.json +25 -0
- package/src/apps/workflow/public/editor/canvas.js +366 -0
- package/src/apps/workflow/public/editor/drag-manager.js +326 -0
- package/src/apps/workflow/public/editor/edge-renderer.js +235 -0
- package/src/apps/workflow/public/editor/history-manager.js +147 -0
- package/src/apps/workflow/public/editor/layout-engine.js +159 -0
- package/src/apps/workflow/public/editor/node-renderer.js +199 -0
- package/src/apps/workflow/public/editor/selection-manager.js +193 -0
- package/src/apps/workflow/public/favicon.svg +7 -0
- package/src/apps/workflow/public/models/node-types.js +300 -0
- package/src/apps/workflow/public/models/workflow-model.js +257 -0
- package/src/apps/workflow/public/page.css +406 -0
- package/src/apps/workflow/public/page.js +658 -0
- package/src/apps/workflow/public/panels/inspector.js +360 -0
- package/src/apps/workflow/public/panels/palette.js +106 -0
- package/src/apps/workflow/public/panels/run-view.js +275 -0
- package/src/apps/workflow/public/panels/toolbar.js +232 -0
- package/src/apps/workflow/public/panels/workflow-list.js +237 -0
- package/src/apps/workflow/public/state/store.js +47 -0
- package/src/apps/workflow/services/custom-node-loader.ts +48 -0
- package/src/apps/workflow/services/legacy-converter.ts +72 -0
- package/src/apps/workflow/services/run-manager.ts +190 -0
- package/src/apps/workflow/services/workflow-store.ts +424 -0
- package/src/apps/workflow/services/workflow-validator.test.ts +103 -0
- package/src/apps/workflow/services/workflow-validator.ts +98 -0
- package/src/apps/workflow/src/index.ts +10 -0
- package/src/apps/workflow/templates/ci-pipeline.json +18 -0
- package/src/apps/workflow/templates/code-review.json +22 -0
- package/src/apps/workflow/templates/kanban-testing.json +24 -0
- package/src/apps/workflow/tsconfig.json +8 -0
- package/src/apps/workflow/types.ts +268 -0
- package/src/packages/auth-middleware.ts +14 -0
- package/src/packages/design-tokens/.turbo/turbo-build.log +10 -0
- package/src/packages/design-tokens/STYLEGUIDE.md +414 -0
- package/src/packages/design-tokens/build.js +413 -0
- package/src/packages/design-tokens/demo/index.html +1367 -0
- package/src/packages/design-tokens/demo/proposition-a.html +717 -0
- package/src/packages/design-tokens/demo/proposition-b.html +1239 -0
- package/src/packages/design-tokens/demo/proposition-c.html +1049 -0
- package/src/packages/design-tokens/dist/tailwind-preset.js +115 -0
- package/src/packages/design-tokens/dist/tokens.css +345 -0
- package/src/packages/design-tokens/dist/tokens.d.ts +229 -0
- package/src/packages/design-tokens/dist/tokens.js +386 -0
- package/src/packages/design-tokens/package.json +25 -0
- package/src/packages/design-tokens/tokens.json +228 -0
- package/src/packages/devtools-middleware.ts +22 -0
- package/src/packages/eslint-config/index.js +63 -0
- package/src/packages/eslint-config/node_modules/.bin/eslint +21 -0
- package/src/packages/eslint-config/package.json +18 -0
- package/src/packages/json-file-store.ts +232 -0
- package/src/packages/mcp-utils/.turbo/turbo-build.log +5 -0
- package/src/packages/mcp-utils/dist/index.d.ts +33 -0
- package/src/packages/mcp-utils/dist/index.d.ts.map +1 -0
- package/src/packages/mcp-utils/dist/index.js +126 -0
- package/src/packages/mcp-utils/dist/index.js.map +1 -0
- package/src/packages/mcp-utils/node_modules/.bin/tsc +21 -0
- package/src/packages/mcp-utils/node_modules/.bin/tsserver +21 -0
- package/src/packages/mcp-utils/package.json +32 -0
- package/src/packages/mcp-utils/src/index.ts +171 -0
- package/src/packages/mcp-utils/tsconfig.json +9 -0
- package/src/packages/paths.ts +18 -0
- package/src/packages/project-context/index.js +55 -0
- package/src/packages/project-context/package.json +13 -0
- package/src/packages/project-store.ts +127 -0
- package/src/packages/server-sniffer.ts +132 -0
- package/src/packages/shared-assets/favicon.svg +7 -0
- package/src/packages/shared-assets/keymap-registry.js +512 -0
- package/src/packages/shared-assets/logo.svg +6 -0
- package/src/packages/shared-assets/package.json +11 -0
- package/src/packages/shared-assets/ui-utils.js +48 -0
- package/src/packages/shared-assets/voice-widget.d.ts +37 -0
- package/src/packages/shared-assets/voice-widget.js +695 -0
- package/src/packages/shared-types/.turbo/turbo-build.log +5 -0
- package/src/packages/shared-types/dist/index.d.ts +39 -0
- package/src/packages/shared-types/dist/index.d.ts.map +1 -0
- package/src/packages/shared-types/node_modules/.bin/tsc +21 -0
- package/src/packages/shared-types/node_modules/.bin/tsserver +21 -0
- package/src/packages/shared-types/package.json +25 -0
- package/src/packages/shared-types/src/index.ts +41 -0
- package/src/packages/shared-types/tsconfig.json +11 -0
- package/src/packages/tsconfig/base.json +15 -0
- package/src/packages/tsconfig/next.json +14 -0
- package/src/packages/tsconfig/node.json +11 -0
- package/src/packages/tsconfig/package.json +10 -0
- 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,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,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
|
+
}
|