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,150 @@
|
|
|
1
|
+
import pty, { type IPty } from 'node-pty';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { createShellMcpServer } from './mcp.js';
|
|
4
|
+
import { runStdio } from '@devglide/mcp-utils';
|
|
5
|
+
|
|
6
|
+
import type { PtyEntry, PaneInfo, DashboardState, ShellConfig, McpState } from './shell-types.js';
|
|
7
|
+
|
|
8
|
+
export type { PtyEntry, PaneInfo, DashboardState, ShellConfig, McpState };
|
|
9
|
+
|
|
10
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
function readCwd(pid: number): Promise<string | null> {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
fs.readlink(`/proc/${pid}/cwd`, (err: NodeJS.ErrnoException | null, linkPath: string) => resolve(err ? null : linkPath));
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ENV_ALLOWLIST: string[] = ['HOME', 'PATH', 'USER', 'SHELL', 'LANG', 'LC_ALL', 'SSH_AUTH_SOCK'];
|
|
19
|
+
|
|
20
|
+
function safeEnv(extra: Record<string, string> = {}): Record<string, string> {
|
|
21
|
+
const env: Record<string, string> = { TERM: 'xterm-256color' };
|
|
22
|
+
for (const key of ENV_ALLOWLIST) {
|
|
23
|
+
if (process.env[key] !== undefined) env[key] = process.env[key]!;
|
|
24
|
+
}
|
|
25
|
+
return { ...env, ...extra };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ── No-op emitter (standalone MCP mode has no socket clients) ────────────────
|
|
29
|
+
const io = { to(_room: string) { return this; }, emit(_event: string, _data?: unknown) { return this; }, close() {} };
|
|
30
|
+
|
|
31
|
+
// ── Shell configs ─────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
const SHELL_CONFIGS: Record<string, ShellConfig> = {
|
|
34
|
+
bash: {
|
|
35
|
+
command: 'bash',
|
|
36
|
+
args: [],
|
|
37
|
+
env: safeEnv()
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// ── Global shared state (survives individual socket disconnects) ───────────────
|
|
42
|
+
|
|
43
|
+
const globalPtys: Map<string, PtyEntry> = new Map(); // id -> { ptyProcess, chunks, totalLen }
|
|
44
|
+
|
|
45
|
+
const dashboardState: DashboardState = {
|
|
46
|
+
panes: [], // [{ id, shellType, title, num, cwd }] — ordered
|
|
47
|
+
activeTab: 'grid',
|
|
48
|
+
activePaneId: null,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
let paneIdCounter: number = 0; // strictly for unique IDs, never reused
|
|
52
|
+
function nextPaneId(): string { return `pane-${++paneIdCounter}`; }
|
|
53
|
+
const SCROLLBACK_LIMIT: number = 200_000; // bytes
|
|
54
|
+
const MAX_PANES: number = 9;
|
|
55
|
+
|
|
56
|
+
// ── Multi-client resize arbitration ──────────────────────────────────────────
|
|
57
|
+
const paneActiveSocket: Map<string, string> = new Map(); // paneId -> socketId
|
|
58
|
+
const socketDimensions: Map<string, Map<string, { cols: number; rows: number }>> = new Map(); // socketId -> Map<paneId, {cols, rows}>
|
|
59
|
+
|
|
60
|
+
// ── PTY lifecycle ─────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
function spawnGlobalPty(
|
|
63
|
+
id: string,
|
|
64
|
+
command: string,
|
|
65
|
+
args: string[],
|
|
66
|
+
env: Record<string, string>,
|
|
67
|
+
cols: number,
|
|
68
|
+
rows: number,
|
|
69
|
+
trackCwd: boolean,
|
|
70
|
+
oscOnly: boolean,
|
|
71
|
+
startCwd: string | null
|
|
72
|
+
): IPty {
|
|
73
|
+
cols = Number.isInteger(cols) && cols >= 1 ? Math.min(cols, 500) : 80;
|
|
74
|
+
rows = Number.isInteger(rows) && rows >= 1 ? Math.min(rows, 500) : 24;
|
|
75
|
+
|
|
76
|
+
const ptyProcess: IPty = pty.spawn(command, args, {
|
|
77
|
+
name: 'xterm-256color',
|
|
78
|
+
cols,
|
|
79
|
+
rows,
|
|
80
|
+
cwd: startCwd || process.env.HOME || '/',
|
|
81
|
+
env
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const entry: PtyEntry = { ptyProcess, chunks: [], totalLen: 0 };
|
|
85
|
+
globalPtys.set(id, entry);
|
|
86
|
+
|
|
87
|
+
let cwdTimer: ReturnType<typeof setTimeout> | null = null;
|
|
88
|
+
|
|
89
|
+
ptyProcess.onData((data: string) => {
|
|
90
|
+
entry.chunks.push(data);
|
|
91
|
+
entry.totalLen += data.length;
|
|
92
|
+
if (entry.totalLen > SCROLLBACK_LIMIT * 1.5) {
|
|
93
|
+
const joined = entry.chunks.join('').slice(-SCROLLBACK_LIMIT);
|
|
94
|
+
entry.chunks = [joined];
|
|
95
|
+
entry.totalLen = joined.length;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
io.to(`pane:${id}`).emit('terminal:data', { id, data });
|
|
99
|
+
|
|
100
|
+
if (trackCwd) {
|
|
101
|
+
const oscMatch = data.match(/\x1b\]7;([^\x07\x1b]+)\x07/);
|
|
102
|
+
if (oscMatch) {
|
|
103
|
+
const cwd = oscMatch[1];
|
|
104
|
+
_updatePaneCwd(id, cwd);
|
|
105
|
+
io.emit('terminal:cwd', { id, cwd });
|
|
106
|
+
} else if (!oscOnly) {
|
|
107
|
+
if (cwdTimer) clearTimeout(cwdTimer);
|
|
108
|
+
cwdTimer = setTimeout(async () => {
|
|
109
|
+
if (!globalPtys.has(id)) return; // PTY exited before timer fired
|
|
110
|
+
const cwd = await readCwd(ptyProcess.pid);
|
|
111
|
+
if (cwd) {
|
|
112
|
+
_updatePaneCwd(id, cwd);
|
|
113
|
+
io.emit('terminal:cwd', { id, cwd });
|
|
114
|
+
}
|
|
115
|
+
}, 300);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
ptyProcess.onExit(({ exitCode }: { exitCode: number }) => {
|
|
121
|
+
if (cwdTimer) clearTimeout(cwdTimer);
|
|
122
|
+
if (!globalPtys.delete(id)) return; // already cleaned up by terminal:close
|
|
123
|
+
io.emit('terminal:exit', { id, code: exitCode });
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return ptyProcess;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function _updatePaneCwd(id: string, cwd: string): void {
|
|
130
|
+
const pane = dashboardState.panes.find((p: PaneInfo) => p.id === id);
|
|
131
|
+
if (pane) pane.cwd = cwd;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── Graceful shutdown ──────────────────────────────────────────────────────────
|
|
135
|
+
function shutdown(): void {
|
|
136
|
+
console.log('\nShutting down — killing PTY processes...');
|
|
137
|
+
for (const { ptyProcess } of globalPtys.values()) {
|
|
138
|
+
try { ptyProcess.kill(); } catch {}
|
|
139
|
+
}
|
|
140
|
+
globalPtys.clear();
|
|
141
|
+
process.exit(0);
|
|
142
|
+
}
|
|
143
|
+
process.on('SIGTERM', shutdown);
|
|
144
|
+
process.on('SIGINT', shutdown);
|
|
145
|
+
|
|
146
|
+
// ── Stdio MCP mode ───────────────────────────────────────────────────────────
|
|
147
|
+
const mcpState: McpState = { globalPtys, dashboardState, io, spawnGlobalPty, SHELL_CONFIGS, MAX_PANES, nextPaneId, paneActiveSocket, socketDimensions };
|
|
148
|
+
const mcpServer = createShellMcpServer(mcpState);
|
|
149
|
+
await runStdio(mcpServer);
|
|
150
|
+
console.error('Devglide Shell MCP server running on stdio');
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { createDevglideMcpServer } from '../../../packages/mcp-utils/src/index.js';
|
|
5
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import type { Express, Request, Response } from 'express';
|
|
9
|
+
import type { McpState, PaneInfo } from './shell-types.js';
|
|
10
|
+
import { getActiveProject } from '../../../project-context.js';
|
|
11
|
+
|
|
12
|
+
/** Send SIGHUP, then SIGKILL after 2 s if still alive. */
|
|
13
|
+
function killPty(pty: { pid: number; kill(signal?: string): void }): void {
|
|
14
|
+
try {
|
|
15
|
+
pty.kill();
|
|
16
|
+
} catch {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const { pid } = pty;
|
|
20
|
+
setTimeout(() => {
|
|
21
|
+
try {
|
|
22
|
+
process.kill(pid, 0);
|
|
23
|
+
process.kill(pid, 'SIGKILL');
|
|
24
|
+
} catch { /* already exited */ }
|
|
25
|
+
}, 2000).unref();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface McpSession {
|
|
29
|
+
transport: StreamableHTTPServerTransport;
|
|
30
|
+
server: McpServer;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create a shell MCP server with terminal management tools.
|
|
35
|
+
*/
|
|
36
|
+
export function createShellMcpServer(state: McpState): McpServer {
|
|
37
|
+
const server = createDevglideMcpServer('devglide-shell', '0.1.0');
|
|
38
|
+
|
|
39
|
+
server.tool(
|
|
40
|
+
'shell_list_panes',
|
|
41
|
+
'List active terminal panes with CWD',
|
|
42
|
+
{},
|
|
43
|
+
async () => {
|
|
44
|
+
const panes = state.dashboardState.panes.map((p) => ({
|
|
45
|
+
id: p.id,
|
|
46
|
+
num: p.num,
|
|
47
|
+
shellType: p.shellType,
|
|
48
|
+
title: p.title,
|
|
49
|
+
cwd: p.cwd,
|
|
50
|
+
}));
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: 'text' as const, text: JSON.stringify(panes, null, 2) }],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
server.tool(
|
|
58
|
+
'shell_create_pane',
|
|
59
|
+
'Create a new terminal pane (uses system default shell, or specify bash)',
|
|
60
|
+
{
|
|
61
|
+
shellType: z
|
|
62
|
+
.enum(['default', 'bash'])
|
|
63
|
+
.optional()
|
|
64
|
+
.describe('Shell type (default: system shell from $SHELL)'),
|
|
65
|
+
cwd: z.string().optional().describe('Working directory'),
|
|
66
|
+
},
|
|
67
|
+
async ({ shellType = 'default', cwd }) => {
|
|
68
|
+
if (state.globalPtys.size >= state.MAX_PANES) {
|
|
69
|
+
return {
|
|
70
|
+
content: [
|
|
71
|
+
{
|
|
72
|
+
type: 'text' as const,
|
|
73
|
+
text: `Maximum pane limit (${state.MAX_PANES}) reached`,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
isError: true,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const id = state.nextPaneId();
|
|
81
|
+
const num = state.dashboardState.panes.length + 1;
|
|
82
|
+
const title = String(num);
|
|
83
|
+
const config = state.SHELL_CONFIGS[shellType] || state.SHELL_CONFIGS.default;
|
|
84
|
+
let args = config.args;
|
|
85
|
+
let startCwd = process.env.HOME || '/';
|
|
86
|
+
|
|
87
|
+
if (cwd) {
|
|
88
|
+
if (!path.isAbsolute(cwd) || cwd.includes('\0') || /\.\.[\\/]/.test(cwd)) {
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: 'text' as const, text: 'Invalid cwd: must be absolute without traversal or null bytes' }],
|
|
91
|
+
isError: true,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const stat = fs.statSync(cwd);
|
|
96
|
+
if (!stat.isDirectory()) throw new Error('not a directory');
|
|
97
|
+
} catch {
|
|
98
|
+
return {
|
|
99
|
+
content: [{ type: 'text' as const, text: 'cwd path does not exist or is not a directory' }],
|
|
100
|
+
isError: true,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
startCwd = cwd;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
console.log(`[shell:mcp] create_pane shell=${shellType} cwd=${startCwd}`);
|
|
108
|
+
|
|
109
|
+
state.spawnGlobalPty(
|
|
110
|
+
id,
|
|
111
|
+
config.command,
|
|
112
|
+
args,
|
|
113
|
+
config.env,
|
|
114
|
+
80,
|
|
115
|
+
24,
|
|
116
|
+
true,
|
|
117
|
+
false,
|
|
118
|
+
startCwd
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const paneInfo: PaneInfo = { id, shellType, title, num, cwd: startCwd, projectId: getActiveProject()?.id || null };
|
|
122
|
+
state.dashboardState.panes.push(paneInfo);
|
|
123
|
+
state.dashboardState.activePaneId = id;
|
|
124
|
+
state.io.emit('state:pane-added', paneInfo);
|
|
125
|
+
state.io.emit('state:active-pane', { paneId: id });
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
content: [
|
|
129
|
+
{ type: 'text' as const, text: JSON.stringify(paneInfo, null, 2) },
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
} catch (err: unknown) {
|
|
133
|
+
return {
|
|
134
|
+
content: [
|
|
135
|
+
{ type: 'text' as const, text: `Failed to start ${shellType}: ${(err as Error).message}` },
|
|
136
|
+
],
|
|
137
|
+
isError: true,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
server.tool(
|
|
144
|
+
'shell_close_pane',
|
|
145
|
+
'Close a terminal pane',
|
|
146
|
+
{
|
|
147
|
+
paneId: z.string().describe("Pane ID (e.g. 'pane-1')"),
|
|
148
|
+
},
|
|
149
|
+
async ({ paneId }) => {
|
|
150
|
+
const entry = state.globalPtys.get(paneId);
|
|
151
|
+
const existed = state.dashboardState.panes.some((p) => p.id === paneId);
|
|
152
|
+
if (!entry && !existed) {
|
|
153
|
+
return {
|
|
154
|
+
content: [{ type: 'text' as const, text: 'Pane not found' }],
|
|
155
|
+
isError: true,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log(`[shell:mcp] close_pane pane=${paneId}`);
|
|
160
|
+
|
|
161
|
+
if (entry) {
|
|
162
|
+
killPty(entry.ptyProcess);
|
|
163
|
+
state.globalPtys.delete(paneId);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Find index of closing pane before removal so we can select the previous one
|
|
167
|
+
const closedIdx = state.dashboardState.panes.findIndex(
|
|
168
|
+
(p) => p.id === paneId
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
state.dashboardState.panes = state.dashboardState.panes.filter(
|
|
172
|
+
(p) => p.id !== paneId
|
|
173
|
+
);
|
|
174
|
+
state.io.emit('state:pane-removed', { id: paneId });
|
|
175
|
+
|
|
176
|
+
// Clean up resize arbitration
|
|
177
|
+
state.paneActiveSocket.delete(paneId);
|
|
178
|
+
if (state.socketDimensions) {
|
|
179
|
+
for (const dims of state.socketDimensions.values()) dims.delete(paneId);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Renumber remaining panes
|
|
183
|
+
state.dashboardState.panes.forEach((p, i) => {
|
|
184
|
+
p.num = i + 1;
|
|
185
|
+
p.title = String(i + 1);
|
|
186
|
+
});
|
|
187
|
+
if (state.dashboardState.panes.length > 0) {
|
|
188
|
+
state.io.emit(
|
|
189
|
+
'state:panes-renumbered',
|
|
190
|
+
state.dashboardState.panes.map(({ id, num }) => ({ id, num }))
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Select the previous pane (or next if closing the first one)
|
|
195
|
+
const prevIdx = Math.max(0, closedIdx - 1);
|
|
196
|
+
const nextPane =
|
|
197
|
+
state.dashboardState.panes.length > 0
|
|
198
|
+
? state.dashboardState.panes[prevIdx].id
|
|
199
|
+
: null;
|
|
200
|
+
|
|
201
|
+
if (state.dashboardState.activeTab === paneId) {
|
|
202
|
+
// The closed pane was the focused tab — navigate to previous pane or back to grid
|
|
203
|
+
const next = nextPane ?? 'grid';
|
|
204
|
+
state.dashboardState.activeTab = next;
|
|
205
|
+
state.dashboardState.activePaneId = nextPane;
|
|
206
|
+
state.io.emit('state:active-tab', { tabId: next });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Always update active pane highlight
|
|
210
|
+
state.dashboardState.activePaneId = nextPane;
|
|
211
|
+
state.io.emit('state:active-pane', { paneId: nextPane });
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
content: [{ type: 'text' as const, text: `Pane ${paneId} closed.` }],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
server.tool(
|
|
220
|
+
'shell_run_command',
|
|
221
|
+
'Send input to a terminal pane and return output after a timeout',
|
|
222
|
+
{
|
|
223
|
+
paneId: z.string().describe("Pane ID (e.g. 'pane-1')"),
|
|
224
|
+
command: z.string().describe('Command to execute'),
|
|
225
|
+
timeout: z
|
|
226
|
+
.number()
|
|
227
|
+
.optional()
|
|
228
|
+
.describe('Seconds to wait for output (default: 3, max: 30)'),
|
|
229
|
+
},
|
|
230
|
+
async ({ paneId, command, timeout }) => {
|
|
231
|
+
const entry = state.globalPtys.get(paneId);
|
|
232
|
+
if (!entry) {
|
|
233
|
+
return {
|
|
234
|
+
content: [{ type: 'text' as const, text: 'Pane not found' }],
|
|
235
|
+
isError: true,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
console.log(`[shell:mcp] run_command pane=${paneId} command=${JSON.stringify(command.slice(0, 200))}`);
|
|
240
|
+
|
|
241
|
+
const maxMs = Math.min((timeout ?? 3) * 1000, 30000);
|
|
242
|
+
// Use tracked totalLen (O(1)) instead of joining all chunks (O(n)) for polling
|
|
243
|
+
const beforeLen = entry.totalLen;
|
|
244
|
+
|
|
245
|
+
entry.ptyProcess.write(command + '\r');
|
|
246
|
+
|
|
247
|
+
// Poll for output quiescence instead of waiting the full timeout
|
|
248
|
+
let lastLen = beforeLen;
|
|
249
|
+
let stableCount = 0;
|
|
250
|
+
const POLL_MS = 100;
|
|
251
|
+
const STABLE_THRESHOLD = 3; // 300ms of no new output = done
|
|
252
|
+
|
|
253
|
+
await new Promise<void>((resolve) => {
|
|
254
|
+
let elapsed = 0;
|
|
255
|
+
const interval = setInterval(() => {
|
|
256
|
+
elapsed += POLL_MS;
|
|
257
|
+
const currentLen = entry.totalLen;
|
|
258
|
+
if (currentLen > lastLen) {
|
|
259
|
+
lastLen = currentLen;
|
|
260
|
+
stableCount = 0;
|
|
261
|
+
} else {
|
|
262
|
+
stableCount++;
|
|
263
|
+
}
|
|
264
|
+
if (stableCount >= STABLE_THRESHOLD || elapsed >= maxMs) {
|
|
265
|
+
clearInterval(interval);
|
|
266
|
+
resolve();
|
|
267
|
+
}
|
|
268
|
+
}, POLL_MS);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Join chunks once at the end to extract new output
|
|
272
|
+
const fullOutput = entry.chunks.join('');
|
|
273
|
+
let newOutput = fullOutput.slice(Math.min(beforeLen, fullOutput.length));
|
|
274
|
+
|
|
275
|
+
// Strip the echoed command line (first line) and ANSI escape sequences
|
|
276
|
+
const lines = newOutput.split('\n');
|
|
277
|
+
if (lines.length > 1) lines.shift(); // remove echoed command
|
|
278
|
+
newOutput = lines.join('\n').replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '').trim();
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
content: [{ type: 'text' as const, text: newOutput || '(no output)' }],
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
server.tool(
|
|
287
|
+
'shell_get_scrollback',
|
|
288
|
+
'Get recent scrollback buffer from a terminal pane',
|
|
289
|
+
{
|
|
290
|
+
paneId: z.string().describe("Pane ID (e.g. 'pane-1')"),
|
|
291
|
+
lines: z
|
|
292
|
+
.number()
|
|
293
|
+
.optional()
|
|
294
|
+
.describe('Number of recent lines to return (default: 100)'),
|
|
295
|
+
},
|
|
296
|
+
async ({ paneId, lines }) => {
|
|
297
|
+
const entry = state.globalPtys.get(paneId);
|
|
298
|
+
if (!entry) {
|
|
299
|
+
return {
|
|
300
|
+
content: [{ type: 'text' as const, text: 'Pane not found' }],
|
|
301
|
+
isError: true,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const limit = lines ?? 100;
|
|
306
|
+
const fullOutput = entry.chunks.join('');
|
|
307
|
+
const allLines = fullOutput.split('\n');
|
|
308
|
+
const recent = allLines.slice(-limit).join('\n');
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
content: [{ type: 'text' as const, text: recent || '(empty)' }],
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
return server;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Mount MCP StreamableHTTP endpoint on an Express app.
|
|
321
|
+
*/
|
|
322
|
+
export function mountShellMcp(app: Express, state: McpState): void {
|
|
323
|
+
const sessions = new Map<string, McpSession>();
|
|
324
|
+
|
|
325
|
+
function isInitReq(body: unknown): boolean {
|
|
326
|
+
if (Array.isArray(body))
|
|
327
|
+
return body.some((m: unknown) => m && typeof m === 'object' && (m as Record<string, unknown>).method === 'initialize');
|
|
328
|
+
return body !== null && typeof body === 'object' && (body as Record<string, unknown>).method === 'initialize';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
app.post('/mcp', async (req: Request, res: Response) => {
|
|
332
|
+
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
|
333
|
+
|
|
334
|
+
if (sessionId && sessions.has(sessionId)) {
|
|
335
|
+
const session = sessions.get(sessionId)!;
|
|
336
|
+
await session.transport.handleRequest(req, res, req.body);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (!sessionId && isInitReq(req.body)) {
|
|
341
|
+
const transport = new StreamableHTTPServerTransport({
|
|
342
|
+
sessionIdGenerator: () => randomUUID(),
|
|
343
|
+
onsessioninitialized: (id: string) => {
|
|
344
|
+
sessions.set(id, { transport, server });
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
transport.onclose = () => {
|
|
348
|
+
if (transport.sessionId) sessions.delete(transport.sessionId);
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const server = createShellMcpServer(state);
|
|
352
|
+
await server.connect(transport);
|
|
353
|
+
await transport.handleRequest(req, res, req.body);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
358
|
+
res.end(
|
|
359
|
+
JSON.stringify({
|
|
360
|
+
jsonrpc: '2.0',
|
|
361
|
+
error: { code: -32000, message: 'Bad Request: No valid session ID' },
|
|
362
|
+
id: null,
|
|
363
|
+
})
|
|
364
|
+
);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
app.get('/mcp', async (req: Request, res: Response) => {
|
|
368
|
+
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
|
369
|
+
if (!sessionId || !sessions.has(sessionId)) {
|
|
370
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
371
|
+
res.end(
|
|
372
|
+
JSON.stringify({
|
|
373
|
+
jsonrpc: '2.0',
|
|
374
|
+
error: { code: -32000, message: 'Bad Request: No valid session ID' },
|
|
375
|
+
id: null,
|
|
376
|
+
})
|
|
377
|
+
);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
await sessions.get(sessionId)!.transport.handleRequest(req, res);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
app.delete('/mcp', async (req: Request, res: Response) => {
|
|
384
|
+
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
|
385
|
+
if (!sessionId || !sessions.has(sessionId)) {
|
|
386
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
387
|
+
res.end(
|
|
388
|
+
JSON.stringify({
|
|
389
|
+
jsonrpc: '2.0',
|
|
390
|
+
error: { code: -32000, message: 'Bad Request: No valid session ID' },
|
|
391
|
+
id: null,
|
|
392
|
+
})
|
|
393
|
+
);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
await sessions.get(sessionId)!.transport.handleRequest(req, res);
|
|
397
|
+
});
|
|
398
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { IPty } from 'node-pty';
|
|
2
|
+
|
|
3
|
+
export interface PtyEntry {
|
|
4
|
+
ptyProcess: IPty;
|
|
5
|
+
chunks: string[];
|
|
6
|
+
totalLen: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface PaneInfo {
|
|
10
|
+
id: string;
|
|
11
|
+
shellType: string;
|
|
12
|
+
title: string;
|
|
13
|
+
num: number;
|
|
14
|
+
cwd: string | null;
|
|
15
|
+
url?: string;
|
|
16
|
+
projectId: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DashboardState {
|
|
20
|
+
panes: PaneInfo[];
|
|
21
|
+
activeTab: string;
|
|
22
|
+
activePaneId: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ShellConfig {
|
|
26
|
+
command: string;
|
|
27
|
+
args: string[];
|
|
28
|
+
env: Record<string, string>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface McpState {
|
|
32
|
+
globalPtys: Map<string, PtyEntry>;
|
|
33
|
+
dashboardState: DashboardState;
|
|
34
|
+
io: any;
|
|
35
|
+
spawnGlobalPty: (id: string, command: string, args: string[], env: Record<string, string>, cols: number, rows: number, trackCwd: boolean, oscOnly: boolean, startCwd: string | null) => IPty;
|
|
36
|
+
SHELL_CONFIGS: Record<string, ShellConfig>;
|
|
37
|
+
MAX_PANES: number;
|
|
38
|
+
nextPaneId: () => string;
|
|
39
|
+
paneActiveSocket: Map<string, string>;
|
|
40
|
+
socketDimensions: Map<string, Map<string, { cols: number; rows: number }>>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
WARN Issue while reading "/home/runner/_work/devglide/devglide/.npmrc". Failed to replace env in config: ${NODE_AUTH_TOKEN}
|
|
2
|
+
|
|
3
|
+
> @devglide/test@0.1.0 lint /home/runner/_work/devglide/devglide/src/apps/test
|
|
4
|
+
> eslint .
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/home/runner/_work/devglide/devglide/src/apps/test/src/index.ts
|
|
8
|
+
1:1 warning There should be at least one empty line between import groups import/order
|
|
9
|
+
2:1 warning `@devglide/mcp-utils` import should occur before import of `./mcp.js` import/order
|
|
10
|
+
|
|
11
|
+
/home/runner/_work/devglide/devglide/src/apps/test/src/mcp.ts
|
|
12
|
+
1:1 warning There should be at least one empty line between import groups import/order
|
|
13
|
+
2:1 warning There should be at least one empty line between import groups import/order
|
|
14
|
+
|
|
15
|
+
/home/runner/_work/devglide/devglide/src/apps/test/src/routes/trigger.ts
|
|
16
|
+
1:1 warning There should be at least one empty line between import groups import/order
|
|
17
|
+
3:1 warning There should be at least one empty line between import groups import/order
|
|
18
|
+
|
|
19
|
+
/home/runner/_work/devglide/devglide/src/apps/test/src/services/scenario-manager.ts
|
|
20
|
+
1:1 warning There should be at least one empty line between import groups import/order
|
|
21
|
+
|
|
22
|
+
/home/runner/_work/devglide/devglide/src/apps/test/src/services/scenario-store.ts
|
|
23
|
+
3:1 warning There should be at least one empty line between import groups import/order
|
|
24
|
+
|
|
25
|
+
✖ 8 problems (0 errors, 8 warnings)
|
|
26
|
+
0 errors and 8 warnings potentially fixable with the `--fix` option.
|
|
27
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../typescript/bin/tsc" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../typescript/bin/tsserver" "$@"
|
|
21
|
+
fi
|