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,171 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
|
+
import { randomUUID } from "crypto";
|
|
5
|
+
|
|
6
|
+
/** Format a JSON-serializable value as an MCP text result. */
|
|
7
|
+
export function jsonResult(data: unknown) {
|
|
8
|
+
return {
|
|
9
|
+
content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Format an error message as an MCP error result. */
|
|
14
|
+
export function errorResult(message: string) {
|
|
15
|
+
return {
|
|
16
|
+
content: [{ type: "text" as const, text: message }],
|
|
17
|
+
isError: true,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createDevglideMcpServer(
|
|
22
|
+
name: string,
|
|
23
|
+
version: string,
|
|
24
|
+
description?: string,
|
|
25
|
+
options?: { instructions?: string | string[] }
|
|
26
|
+
): McpServer {
|
|
27
|
+
const serverOpts = options
|
|
28
|
+
? {
|
|
29
|
+
...options,
|
|
30
|
+
instructions: Array.isArray(options.instructions)
|
|
31
|
+
? options.instructions.join('\n')
|
|
32
|
+
: options.instructions,
|
|
33
|
+
}
|
|
34
|
+
: undefined;
|
|
35
|
+
return new McpServer(
|
|
36
|
+
{ name, version, ...(description && { description }) },
|
|
37
|
+
...(serverOpts ? [serverOpts] : [])
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isInitializeRequest(body: unknown): boolean {
|
|
42
|
+
if (Array.isArray(body)) {
|
|
43
|
+
return body.some(
|
|
44
|
+
(msg) =>
|
|
45
|
+
typeof msg === "object" &&
|
|
46
|
+
msg !== null &&
|
|
47
|
+
(msg as Record<string, unknown>).method === "initialize"
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return (
|
|
51
|
+
typeof body === "object" &&
|
|
52
|
+
body !== null &&
|
|
53
|
+
(body as Record<string, unknown>).method === "initialize"
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface McpSession {
|
|
58
|
+
transport: StreamableHTTPServerTransport;
|
|
59
|
+
server: McpServer;
|
|
60
|
+
lastAccessed: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Mount an MCP StreamableHTTP endpoint on an Express-compatible app.
|
|
65
|
+
* Each session gets its own McpServer instance via the factory function.
|
|
66
|
+
*/
|
|
67
|
+
export function mountMcpHttp(
|
|
68
|
+
app: {
|
|
69
|
+
post: (path: string, handler: (...args: any[]) => any) => void;
|
|
70
|
+
get: (path: string, handler: (...args: any[]) => any) => void;
|
|
71
|
+
delete: (path: string, handler: (...args: any[]) => any) => void;
|
|
72
|
+
},
|
|
73
|
+
serverFactory: () => McpServer,
|
|
74
|
+
path: string = "/mcp"
|
|
75
|
+
): void {
|
|
76
|
+
const sessions = new Map<string, McpSession>();
|
|
77
|
+
|
|
78
|
+
// TTL cleanup: remove sessions not accessed in 30 minutes
|
|
79
|
+
const SESSION_TTL_MS = 30 * 60 * 1000;
|
|
80
|
+
const ttlTimer = setInterval(() => {
|
|
81
|
+
const cutoff = Date.now() - SESSION_TTL_MS;
|
|
82
|
+
for (const [id, session] of sessions) {
|
|
83
|
+
if (session.lastAccessed < cutoff) {
|
|
84
|
+
session.transport.close?.();
|
|
85
|
+
sessions.delete(id);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}, 5 * 60 * 1000);
|
|
89
|
+
ttlTimer.unref();
|
|
90
|
+
|
|
91
|
+
app.post(path, async (req: any, res: any) => {
|
|
92
|
+
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
|
93
|
+
|
|
94
|
+
if (sessionId && sessions.has(sessionId)) {
|
|
95
|
+
const session = sessions.get(sessionId)!;
|
|
96
|
+
session.lastAccessed = Date.now();
|
|
97
|
+
await session.transport.handleRequest(req, res, req.body);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!sessionId && isInitializeRequest(req.body)) {
|
|
102
|
+
const transport = new StreamableHTTPServerTransport({
|
|
103
|
+
sessionIdGenerator: () => randomUUID(),
|
|
104
|
+
onsessioninitialized: (id: string) => {
|
|
105
|
+
sessions.set(id, { transport, server, lastAccessed: Date.now() });
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
transport.onclose = () => {
|
|
109
|
+
if (transport.sessionId) sessions.delete(transport.sessionId);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const server = serverFactory();
|
|
113
|
+
await server.connect(transport);
|
|
114
|
+
await transport.handleRequest(req, res, req.body);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
119
|
+
res.end(
|
|
120
|
+
JSON.stringify({
|
|
121
|
+
jsonrpc: "2.0",
|
|
122
|
+
error: { code: -32000, message: "Bad Request: No valid session ID" },
|
|
123
|
+
id: null,
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
app.get(path, async (req: any, res: any) => {
|
|
129
|
+
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
|
130
|
+
if (!sessionId || !sessions.has(sessionId)) {
|
|
131
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
132
|
+
res.end(
|
|
133
|
+
JSON.stringify({
|
|
134
|
+
jsonrpc: "2.0",
|
|
135
|
+
error: { code: -32000, message: "Bad Request: No valid session ID" },
|
|
136
|
+
id: null,
|
|
137
|
+
})
|
|
138
|
+
);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const getSession = sessions.get(sessionId)!;
|
|
142
|
+
getSession.lastAccessed = Date.now();
|
|
143
|
+
await getSession.transport.handleRequest(req, res);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
app.delete(path, async (req: any, res: any) => {
|
|
147
|
+
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
|
148
|
+
if (!sessionId || !sessions.has(sessionId)) {
|
|
149
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
150
|
+
res.end(
|
|
151
|
+
JSON.stringify({
|
|
152
|
+
jsonrpc: "2.0",
|
|
153
|
+
error: { code: -32000, message: "Bad Request: No valid session ID" },
|
|
154
|
+
id: null,
|
|
155
|
+
})
|
|
156
|
+
);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const delSession = sessions.get(sessionId)!;
|
|
160
|
+
delSession.lastAccessed = Date.now();
|
|
161
|
+
await delSession.transport.handleRequest(req, res);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Run an MCP server in stdio mode (for Claude Desktop / CLI usage).
|
|
167
|
+
*/
|
|
168
|
+
export async function runStdio(server: McpServer): Promise<void> {
|
|
169
|
+
const transport = new StdioServerTransport();
|
|
170
|
+
await server.connect(transport);
|
|
171
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized path constants for ~/.devglide subdirectories.
|
|
3
|
+
* Single source of truth — all apps import from here.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
|
|
9
|
+
export const DEVGLIDE_DIR: string = join(homedir(), '.devglide');
|
|
10
|
+
export const DATABASES_DIR: string = join(DEVGLIDE_DIR, 'databases');
|
|
11
|
+
export const WORKFLOWS_DIR: string = join(DEVGLIDE_DIR, 'workflows');
|
|
12
|
+
export const INSTRUCTIONS_DIR: string = join(DEVGLIDE_DIR, 'instructions');
|
|
13
|
+
export const VOCABULARY_DIR: string = join(DEVGLIDE_DIR, 'vocabulary');
|
|
14
|
+
export const VOICE_DIR: string = join(DEVGLIDE_DIR, 'voice');
|
|
15
|
+
export const LOGS_DIR: string = join(DEVGLIDE_DIR, 'logs');
|
|
16
|
+
export const PROJECTS_FILE: string = join(DEVGLIDE_DIR, 'projects.json');
|
|
17
|
+
export const PROMPTS_DIR: string = join(DEVGLIDE_DIR, 'prompts');
|
|
18
|
+
export const DOCS_DIR: string = join(DEVGLIDE_DIR, 'documentation');
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared project context consumer — connects to Dashboard for active project tracking.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import { connectProjectContext } from '../../packages/project-context.js';
|
|
6
|
+
*
|
|
7
|
+
* const projectCtx = connectProjectContext({ service: 'log' });
|
|
8
|
+
* projectCtx.active; // current project or null
|
|
9
|
+
* projectCtx.onChange(cb); // subscribe to project changes
|
|
10
|
+
* projectCtx.disconnect(); // for shutdown
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { io as ioClient } from 'socket.io-client';
|
|
14
|
+
|
|
15
|
+
const DASHBOARD_URL = 'http://localhost:7000';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Connect to Dashboard for project context.
|
|
19
|
+
* @param {{ service: string, port?: number }} opts
|
|
20
|
+
* @returns {{ active: object|null, onChange: (cb: function) => function, disconnect: () => void }}
|
|
21
|
+
*/
|
|
22
|
+
export function connectProjectContext({ service, port } = {}) {
|
|
23
|
+
const url = port ? `http://localhost:${port}` : DASHBOARD_URL;
|
|
24
|
+
const socket = ioClient(url, { reconnection: true });
|
|
25
|
+
|
|
26
|
+
let active = null;
|
|
27
|
+
const listeners = new Set();
|
|
28
|
+
|
|
29
|
+
socket.on('project:active', (project) => {
|
|
30
|
+
active = project;
|
|
31
|
+
for (const cb of listeners) cb(project);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
socket.on('connect', () => {
|
|
35
|
+
if (service) console.log(`[${service}] Connected to Dashboard for project context`);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
socket.on('connect_error', () => {
|
|
39
|
+
// Dashboard may not be running — silently retry
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
get active() { return active; },
|
|
44
|
+
|
|
45
|
+
/** Subscribe to project changes. Returns an unsubscribe function. */
|
|
46
|
+
onChange(cb) {
|
|
47
|
+
listeners.add(cb);
|
|
48
|
+
return () => listeners.delete(cb);
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
disconnect() {
|
|
52
|
+
socket.disconnect();
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@devglide/project-context",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared project context consumer for devglide apps",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"socket.io-client": "^4.8.0"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { randomUUID } from 'crypto';
|
|
4
|
+
import { DEVGLIDE_DIR, PROJECTS_FILE } from './paths.js';
|
|
5
|
+
|
|
6
|
+
const STORE_PATH = PROJECTS_FILE;
|
|
7
|
+
|
|
8
|
+
export interface Project {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
path: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface StoreData {
|
|
15
|
+
projects: Project[];
|
|
16
|
+
activeProjectId: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let _cache: StoreData | null = null;
|
|
20
|
+
|
|
21
|
+
function ensureLoaded(): StoreData {
|
|
22
|
+
if (_cache) return _cache;
|
|
23
|
+
try {
|
|
24
|
+
_cache = JSON.parse(fs.readFileSync(STORE_PATH, 'utf8'));
|
|
25
|
+
} catch {
|
|
26
|
+
_cache = { projects: [], activeProjectId: null };
|
|
27
|
+
}
|
|
28
|
+
return _cache!;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function flushStore(): void {
|
|
32
|
+
fs.mkdirSync(DEVGLIDE_DIR, { recursive: true });
|
|
33
|
+
fs.writeFileSync(STORE_PATH, JSON.stringify(_cache, null, 2), 'utf8');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function listProjects(): StoreData {
|
|
37
|
+
return ensureLoaded();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function addProject(name: string, projectPath: string): Project {
|
|
41
|
+
if (!name || typeof name !== 'string') throw new Error('name is required');
|
|
42
|
+
if (!projectPath || typeof projectPath !== 'string') throw new Error('path is required');
|
|
43
|
+
|
|
44
|
+
const absPath = path.resolve(projectPath);
|
|
45
|
+
if (!fs.existsSync(absPath) || !fs.statSync(absPath).isDirectory()) {
|
|
46
|
+
throw new Error(`Path does not exist or is not a directory: ${absPath}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const store = ensureLoaded();
|
|
50
|
+
if (store.projects.some(p => p.path === absPath)) {
|
|
51
|
+
throw new Error(`Project with path "${absPath}" already exists`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const project: Project = { id: randomUUID(), name: name.trim(), path: absPath };
|
|
55
|
+
store.projects.push(project);
|
|
56
|
+
|
|
57
|
+
if (!store.activeProjectId) store.activeProjectId = project.id;
|
|
58
|
+
|
|
59
|
+
flushStore();
|
|
60
|
+
return project;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function removeProject(id: string): boolean {
|
|
64
|
+
const store = ensureLoaded();
|
|
65
|
+
const before = store.projects.length;
|
|
66
|
+
store.projects = store.projects.filter(p => p.id !== id);
|
|
67
|
+
if (store.projects.length === before) return false;
|
|
68
|
+
|
|
69
|
+
if (store.activeProjectId === id) {
|
|
70
|
+
store.activeProjectId = store.projects.length > 0 ? store.projects[0].id : null;
|
|
71
|
+
}
|
|
72
|
+
flushStore();
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function activateProject(id: string): Project | null {
|
|
77
|
+
const store = ensureLoaded();
|
|
78
|
+
const project = store.projects.find(p => p.id === id);
|
|
79
|
+
if (!project) return null;
|
|
80
|
+
|
|
81
|
+
store.activeProjectId = id;
|
|
82
|
+
flushStore();
|
|
83
|
+
return project;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function updateProject(id: string, updates: { name?: string; path?: string }): Project {
|
|
87
|
+
const store = ensureLoaded();
|
|
88
|
+
const project = store.projects.find(p => p.id === id);
|
|
89
|
+
if (!project) throw new Error('Project not found');
|
|
90
|
+
|
|
91
|
+
if (updates.name !== undefined) {
|
|
92
|
+
if (!updates.name || typeof updates.name !== 'string') {
|
|
93
|
+
throw new Error('name must be a non-empty string');
|
|
94
|
+
}
|
|
95
|
+
project.name = updates.name.trim();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (updates.path !== undefined) {
|
|
99
|
+
if (!updates.path || typeof updates.path !== 'string') {
|
|
100
|
+
throw new Error('path must be a non-empty string');
|
|
101
|
+
}
|
|
102
|
+
const absPath = path.resolve(updates.path);
|
|
103
|
+
if (!path.isAbsolute(absPath)) {
|
|
104
|
+
throw new Error(`Path must be absolute: ${updates.path}`);
|
|
105
|
+
}
|
|
106
|
+
if (!fs.existsSync(absPath) || !fs.statSync(absPath).isDirectory()) {
|
|
107
|
+
throw new Error(`Path does not exist or is not a directory: ${absPath}`);
|
|
108
|
+
}
|
|
109
|
+
if (store.projects.some(p => p.id !== id && p.path === absPath)) {
|
|
110
|
+
throw new Error(`Another project with path "${absPath}" already exists`);
|
|
111
|
+
}
|
|
112
|
+
project.path = absPath;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
flushStore();
|
|
116
|
+
return project;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function getActiveProject(): Project | null {
|
|
120
|
+
const store = ensureLoaded();
|
|
121
|
+
if (!store.activeProjectId) return null;
|
|
122
|
+
return store.projects.find(p => p.id === store.activeProjectId) || null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function readActiveProjectId(): string | null {
|
|
126
|
+
return ensureLoaded().activeProjectId;
|
|
127
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side console sniffer for Devglide apps.
|
|
3
|
+
* Writes log entries directly to disk and forwards them to the Log service.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* import { initServerSniffer } from '../../packages/server-sniffer.js';
|
|
7
|
+
* initServerSniffer({ service: 'kanban', targetPath: '/abs/path/server.log' });
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { writeFileSync, mkdirSync, createWriteStream } from 'fs';
|
|
11
|
+
import { dirname } from 'path';
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export interface ServerSnifferOptions {
|
|
15
|
+
/** Service name (e.g. 'kanban', 'voice') */
|
|
16
|
+
service: string;
|
|
17
|
+
/** Absolute path to the JSONL log file */
|
|
18
|
+
targetPath: string;
|
|
19
|
+
/** Log service port (default: 7000) */
|
|
20
|
+
logPort?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface LogEntry {
|
|
24
|
+
type: string;
|
|
25
|
+
session: string;
|
|
26
|
+
seq: number;
|
|
27
|
+
ts: string;
|
|
28
|
+
message?: string;
|
|
29
|
+
url?: string;
|
|
30
|
+
ua?: string;
|
|
31
|
+
persistent?: boolean;
|
|
32
|
+
targetPath?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let _initialized = false;
|
|
36
|
+
|
|
37
|
+
export function initServerSniffer(opts: ServerSnifferOptions): void {
|
|
38
|
+
if (_initialized) return;
|
|
39
|
+
_initialized = true;
|
|
40
|
+
|
|
41
|
+
const { service, targetPath, logPort = 7000 } = opts;
|
|
42
|
+
const baseUrl = `http://localhost:${logPort}`;
|
|
43
|
+
const sessionId = `${service}-server`;
|
|
44
|
+
|
|
45
|
+
const origLog = console.log;
|
|
46
|
+
const origWarn = console.warn;
|
|
47
|
+
const origError = console.error;
|
|
48
|
+
|
|
49
|
+
let seq = 0;
|
|
50
|
+
|
|
51
|
+
// Ensure target directory exists
|
|
52
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
53
|
+
|
|
54
|
+
// Truncate log file and write SESSION_START directly (sync, runs once)
|
|
55
|
+
const sessionStart: LogEntry = {
|
|
56
|
+
type: 'SESSION_START',
|
|
57
|
+
session: sessionId,
|
|
58
|
+
seq: seq++,
|
|
59
|
+
ts: new Date().toISOString(),
|
|
60
|
+
url: `server://${service}`,
|
|
61
|
+
ua: `node/${process.version}`,
|
|
62
|
+
persistent: true,
|
|
63
|
+
};
|
|
64
|
+
writeFileSync(targetPath, JSON.stringify(sessionStart) + '\n');
|
|
65
|
+
|
|
66
|
+
// Open append stream AFTER truncation for non-blocking writes
|
|
67
|
+
const logStream = createWriteStream(targetPath, { flags: 'a' });
|
|
68
|
+
logStream.on('error', () => {});
|
|
69
|
+
|
|
70
|
+
function writeEntry(entry: LogEntry): void {
|
|
71
|
+
logStream.write(JSON.stringify(entry) + '\n');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function forward(entry: LogEntry): void {
|
|
75
|
+
fetch(`${baseUrl}/api/log`, {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: { 'Content-Type': 'application/json' },
|
|
78
|
+
body: JSON.stringify(entry),
|
|
79
|
+
signal: AbortSignal.timeout(500),
|
|
80
|
+
}).catch(() => {});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
forward({ ...sessionStart, targetPath });
|
|
84
|
+
|
|
85
|
+
function send(type: string, args: unknown[]): void {
|
|
86
|
+
const message = args
|
|
87
|
+
.map((a) => {
|
|
88
|
+
if (typeof a === 'string') return a;
|
|
89
|
+
try { return JSON.stringify(a); } catch { return String(a); }
|
|
90
|
+
})
|
|
91
|
+
.join(' ');
|
|
92
|
+
|
|
93
|
+
const entry: LogEntry = {
|
|
94
|
+
type,
|
|
95
|
+
session: sessionId,
|
|
96
|
+
seq: seq++,
|
|
97
|
+
ts: new Date().toISOString(),
|
|
98
|
+
message,
|
|
99
|
+
persistent: true,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
writeEntry(entry);
|
|
103
|
+
forward({ ...entry, targetPath });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log = function (...args: unknown[]) {
|
|
107
|
+
origLog.apply(console, args);
|
|
108
|
+
send('SERVER_LOG', args);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
console.warn = function (...args: unknown[]) {
|
|
112
|
+
origWarn.apply(console, args);
|
|
113
|
+
send('SERVER_WARN', args);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
console.error = function (...args: unknown[]) {
|
|
117
|
+
origError.apply(console, args);
|
|
118
|
+
send('SERVER_ERROR', args);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
process.on('uncaughtException', (err: Error) => {
|
|
122
|
+
send('SERVER_ERROR', [`Uncaught Exception: ${err.message}\n${err.stack || ''}`]);
|
|
123
|
+
setTimeout(() => process.exit(1), 1000).unref();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
process.on('unhandledRejection', (reason: unknown) => {
|
|
127
|
+
const msg = reason instanceof Error
|
|
128
|
+
? `Unhandled Rejection: ${reason.message}\n${reason.stack || ''}`
|
|
129
|
+
: `Unhandled Rejection: ${String(reason)}`;
|
|
130
|
+
send('SERVER_ERROR', [msg]);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
2
|
+
<rect width="32" height="32" fill="#1c2128"/>
|
|
3
|
+
<rect width="32" height="32" fill="none" stroke="#7ee787" stroke-width="1.5"/>
|
|
4
|
+
<path d="M8 10h6a6 6 0 0 1 0 12H8V10z" fill="#7ee787"/>
|
|
5
|
+
<rect x="18" y="10" width="6" height="4" fill="#7ee787" opacity="0.6"/>
|
|
6
|
+
<rect x="18" y="18" width="6" height="4" fill="#7ee787" opacity="0.6"/>
|
|
7
|
+
</svg>
|