@orkify/cli 1.0.0-beta.5
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 +191 -0
- package/README.md +1701 -0
- package/bin/orkify +3 -0
- package/boot/systemd/orkify@.service +30 -0
- package/dist/agent-name.d.ts +4 -0
- package/dist/agent-name.js +42 -0
- package/dist/alerts/AlertEvaluator.d.ts +14 -0
- package/dist/alerts/AlertEvaluator.js +135 -0
- package/dist/cli/commands/autostart.d.ts +3 -0
- package/dist/cli/commands/autostart.js +11 -0
- package/dist/cli/commands/crash-test.d.ts +3 -0
- package/dist/cli/commands/crash-test.js +17 -0
- package/dist/cli/commands/daemon-reload.d.ts +3 -0
- package/dist/cli/commands/daemon-reload.js +72 -0
- package/dist/cli/commands/delete.d.ts +3 -0
- package/dist/cli/commands/delete.js +37 -0
- package/dist/cli/commands/deploy.d.ts +6 -0
- package/dist/cli/commands/deploy.js +266 -0
- package/dist/cli/commands/down.d.ts +3 -0
- package/dist/cli/commands/down.js +36 -0
- package/dist/cli/commands/flush.d.ts +3 -0
- package/dist/cli/commands/flush.js +28 -0
- package/dist/cli/commands/kill.d.ts +3 -0
- package/dist/cli/commands/kill.js +35 -0
- package/dist/cli/commands/list.d.ts +14 -0
- package/dist/cli/commands/list.js +361 -0
- package/dist/cli/commands/logs.d.ts +3 -0
- package/dist/cli/commands/logs.js +107 -0
- package/dist/cli/commands/mcp.d.ts +3 -0
- package/dist/cli/commands/mcp.js +151 -0
- package/dist/cli/commands/reload.d.ts +3 -0
- package/dist/cli/commands/reload.js +54 -0
- package/dist/cli/commands/restart.d.ts +3 -0
- package/dist/cli/commands/restart.js +43 -0
- package/dist/cli/commands/restore.d.ts +3 -0
- package/dist/cli/commands/restore.js +88 -0
- package/dist/cli/commands/run.d.ts +8 -0
- package/dist/cli/commands/run.js +212 -0
- package/dist/cli/commands/snap.d.ts +3 -0
- package/dist/cli/commands/snap.js +30 -0
- package/dist/cli/commands/up.d.ts +3 -0
- package/dist/cli/commands/up.js +125 -0
- package/dist/cli/crash-recovery.d.ts +2 -0
- package/dist/cli/crash-recovery.js +67 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +46 -0
- package/dist/cli/parse.d.ts +28 -0
- package/dist/cli/parse.js +97 -0
- package/dist/cluster/ClusterWrapper.d.ts +18 -0
- package/dist/cluster/ClusterWrapper.js +602 -0
- package/dist/config/ConfigStore.d.ts +11 -0
- package/dist/config/ConfigStore.js +21 -0
- package/dist/config/schema.d.ts +103 -0
- package/dist/config/schema.js +49 -0
- package/dist/constants.d.ts +83 -0
- package/dist/constants.js +289 -0
- package/dist/cron/CronScheduler.d.ts +25 -0
- package/dist/cron/CronScheduler.js +149 -0
- package/dist/daemon/GracefulManager.d.ts +8 -0
- package/dist/daemon/GracefulManager.js +29 -0
- package/dist/daemon/ManagedProcess.d.ts +71 -0
- package/dist/daemon/ManagedProcess.js +1020 -0
- package/dist/daemon/Orchestrator.d.ts +51 -0
- package/dist/daemon/Orchestrator.js +416 -0
- package/dist/daemon/RotatingWriter.d.ts +27 -0
- package/dist/daemon/RotatingWriter.js +264 -0
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.js +106 -0
- package/dist/daemon/startDaemon.d.ts +30 -0
- package/dist/daemon/startDaemon.js +693 -0
- package/dist/deploy/CommandPoller.d.ts +13 -0
- package/dist/deploy/CommandPoller.js +53 -0
- package/dist/deploy/DeployExecutor.d.ts +33 -0
- package/dist/deploy/DeployExecutor.js +340 -0
- package/dist/deploy/config.d.ts +20 -0
- package/dist/deploy/config.js +161 -0
- package/dist/deploy/env.d.ts +2 -0
- package/dist/deploy/env.js +17 -0
- package/dist/deploy/tarball.d.ts +32 -0
- package/dist/deploy/tarball.js +243 -0
- package/dist/detect/framework.d.ts +2 -0
- package/dist/detect/framework.js +24 -0
- package/dist/ipc/DaemonClient.d.ts +31 -0
- package/dist/ipc/DaemonClient.js +248 -0
- package/dist/ipc/DaemonServer.d.ts +28 -0
- package/dist/ipc/DaemonServer.js +166 -0
- package/dist/ipc/MultiUserClient.d.ts +27 -0
- package/dist/ipc/MultiUserClient.js +203 -0
- package/dist/ipc/protocol.d.ts +7 -0
- package/dist/ipc/protocol.js +53 -0
- package/dist/ipc/restoreDaemon.d.ts +8 -0
- package/dist/ipc/restoreDaemon.js +19 -0
- package/dist/machine-id.d.ts +11 -0
- package/dist/machine-id.js +51 -0
- package/dist/mcp/auth.d.ts +118 -0
- package/dist/mcp/auth.js +245 -0
- package/dist/mcp/http.d.ts +20 -0
- package/dist/mcp/http.js +229 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.js +8 -0
- package/dist/mcp/server.d.ts +37 -0
- package/dist/mcp/server.js +413 -0
- package/dist/probe/compute-fingerprint.d.ts +27 -0
- package/dist/probe/compute-fingerprint.js +65 -0
- package/dist/probe/parse-frames.d.ts +21 -0
- package/dist/probe/parse-frames.js +57 -0
- package/dist/probe/resolve-sourcemaps.d.ts +25 -0
- package/dist/probe/resolve-sourcemaps.js +281 -0
- package/dist/state/StateStore.d.ts +11 -0
- package/dist/state/StateStore.js +78 -0
- package/dist/telemetry/TelemetryReporter.d.ts +49 -0
- package/dist/telemetry/TelemetryReporter.js +451 -0
- package/dist/types/index.d.ts +373 -0
- package/dist/types/index.js +2 -0
- package/package.json +148 -0
- package/packages/cache/README.md +114 -0
- package/packages/cache/dist/CacheClient.d.ts +26 -0
- package/packages/cache/dist/CacheClient.d.ts.map +1 -0
- package/packages/cache/dist/CacheClient.js +174 -0
- package/packages/cache/dist/CacheClient.js.map +1 -0
- package/packages/cache/dist/CacheFileStore.d.ts +45 -0
- package/packages/cache/dist/CacheFileStore.d.ts.map +1 -0
- package/packages/cache/dist/CacheFileStore.js +446 -0
- package/packages/cache/dist/CacheFileStore.js.map +1 -0
- package/packages/cache/dist/CachePersistence.d.ts +9 -0
- package/packages/cache/dist/CachePersistence.d.ts.map +1 -0
- package/packages/cache/dist/CachePersistence.js +67 -0
- package/packages/cache/dist/CachePersistence.js.map +1 -0
- package/packages/cache/dist/CachePrimary.d.ts +25 -0
- package/packages/cache/dist/CachePrimary.d.ts.map +1 -0
- package/packages/cache/dist/CachePrimary.js +155 -0
- package/packages/cache/dist/CachePrimary.js.map +1 -0
- package/packages/cache/dist/CacheStore.d.ts +50 -0
- package/packages/cache/dist/CacheStore.d.ts.map +1 -0
- package/packages/cache/dist/CacheStore.js +271 -0
- package/packages/cache/dist/CacheStore.js.map +1 -0
- package/packages/cache/dist/constants.d.ts +6 -0
- package/packages/cache/dist/constants.d.ts.map +1 -0
- package/packages/cache/dist/constants.js +9 -0
- package/packages/cache/dist/constants.js.map +1 -0
- package/packages/cache/dist/index.d.ts +16 -0
- package/packages/cache/dist/index.d.ts.map +1 -0
- package/packages/cache/dist/index.js +86 -0
- package/packages/cache/dist/index.js.map +1 -0
- package/packages/cache/dist/serialize.d.ts +9 -0
- package/packages/cache/dist/serialize.d.ts.map +1 -0
- package/packages/cache/dist/serialize.js +40 -0
- package/packages/cache/dist/serialize.js.map +1 -0
- package/packages/cache/dist/types.d.ts +123 -0
- package/packages/cache/dist/types.d.ts.map +1 -0
- package/packages/cache/dist/types.js +2 -0
- package/packages/cache/dist/types.js.map +1 -0
- package/packages/cache/package.json +27 -0
- package/packages/cache/src/CacheClient.ts +227 -0
- package/packages/cache/src/CacheFileStore.ts +528 -0
- package/packages/cache/src/CachePersistence.ts +89 -0
- package/packages/cache/src/CachePrimary.ts +172 -0
- package/packages/cache/src/CacheStore.ts +308 -0
- package/packages/cache/src/constants.ts +10 -0
- package/packages/cache/src/index.ts +100 -0
- package/packages/cache/src/serialize.ts +49 -0
- package/packages/cache/src/types.ts +156 -0
- package/packages/cache/tsconfig.json +18 -0
- package/packages/cache/tsconfig.tsbuildinfo +1 -0
- package/packages/next/README.md +166 -0
- package/packages/next/dist/error-capture.d.ts +34 -0
- package/packages/next/dist/error-capture.d.ts.map +1 -0
- package/packages/next/dist/error-capture.js +130 -0
- package/packages/next/dist/error-capture.js.map +1 -0
- package/packages/next/dist/error-handler.d.ts +10 -0
- package/packages/next/dist/error-handler.d.ts.map +1 -0
- package/packages/next/dist/error-handler.js +186 -0
- package/packages/next/dist/error-handler.js.map +1 -0
- package/packages/next/dist/isr-cache.d.ts +9 -0
- package/packages/next/dist/isr-cache.d.ts.map +1 -0
- package/packages/next/dist/isr-cache.js +86 -0
- package/packages/next/dist/isr-cache.js.map +1 -0
- package/packages/next/dist/stream.d.ts +5 -0
- package/packages/next/dist/stream.d.ts.map +1 -0
- package/packages/next/dist/stream.js +22 -0
- package/packages/next/dist/stream.js.map +1 -0
- package/packages/next/dist/types.d.ts +33 -0
- package/packages/next/dist/types.d.ts.map +1 -0
- package/packages/next/dist/types.js +6 -0
- package/packages/next/dist/types.js.map +1 -0
- package/packages/next/dist/use-cache.d.ts +4 -0
- package/packages/next/dist/use-cache.d.ts.map +1 -0
- package/packages/next/dist/use-cache.js +86 -0
- package/packages/next/dist/use-cache.js.map +1 -0
- package/packages/next/dist/utils.d.ts +32 -0
- package/packages/next/dist/utils.d.ts.map +1 -0
- package/packages/next/dist/utils.js +88 -0
- package/packages/next/dist/utils.js.map +1 -0
- package/packages/next/package.json +52 -0
- package/packages/next/src/error-capture.ts +177 -0
- package/packages/next/src/error-handler.ts +221 -0
- package/packages/next/src/isr-cache.ts +100 -0
- package/packages/next/src/stream.ts +23 -0
- package/packages/next/src/types.ts +33 -0
- package/packages/next/src/use-cache.ts +99 -0
- package/packages/next/src/utils.ts +102 -0
- package/packages/next/tsconfig.json +19 -0
- package/packages/next/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { cpus } from 'node:os';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { IPCMessageType } from '../constants.js';
|
|
7
|
+
import { DaemonClient } from '../ipc/DaemonClient.js';
|
|
8
|
+
import { isElevated, listAllUsers } from '../ipc/MultiUserClient.js';
|
|
9
|
+
// Shared IPC client for all MCP sessions. DaemonClient is stateless per-request,
|
|
10
|
+
// so concurrent tool calls from different sessions safely share a single instance.
|
|
11
|
+
const mcpDaemonClient = new DaemonClient();
|
|
12
|
+
/**
|
|
13
|
+
* Format error response for AI consumption
|
|
14
|
+
*/
|
|
15
|
+
function formatError(error, code, context) {
|
|
16
|
+
return {
|
|
17
|
+
content: [
|
|
18
|
+
{
|
|
19
|
+
type: 'text',
|
|
20
|
+
text: JSON.stringify({
|
|
21
|
+
error: code || 'ERROR',
|
|
22
|
+
message: error,
|
|
23
|
+
...context,
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
isError: true,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Format success response for AI consumption
|
|
32
|
+
*/
|
|
33
|
+
function formatSuccess(data) {
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: 'text',
|
|
38
|
+
text: typeof data === 'string' ? data : JSON.stringify(data, null, 2),
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parse workers option (same logic as CLI)
|
|
45
|
+
*/
|
|
46
|
+
function parseWorkers(value) {
|
|
47
|
+
if (value === undefined) {
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
if (value === 0) {
|
|
51
|
+
return cpus().length;
|
|
52
|
+
}
|
|
53
|
+
if (value < 0) {
|
|
54
|
+
return Math.max(1, cpus().length + value);
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Check if a tool is accessible given the current auth context.
|
|
60
|
+
* - No authInfo (stdio mode): always allowed
|
|
61
|
+
* - Scopes include "*": all tools allowed
|
|
62
|
+
* - Otherwise: tool name must be in scopes
|
|
63
|
+
*/
|
|
64
|
+
export function checkToolAccess(toolName, authInfo) {
|
|
65
|
+
if (!authInfo)
|
|
66
|
+
return { allowed: true }; // stdio mode — no auth
|
|
67
|
+
if (authInfo.scopes.includes('*'))
|
|
68
|
+
return { allowed: true };
|
|
69
|
+
if (authInfo.scopes.includes(toolName))
|
|
70
|
+
return { allowed: true };
|
|
71
|
+
return {
|
|
72
|
+
allowed: false,
|
|
73
|
+
error: formatError(`Token "${authInfo.clientId}" does not have access to "${toolName}"`, 'FORBIDDEN'),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Create and configure the MCP server with all ORKIFY tools.
|
|
78
|
+
* When authInfo is provided (HTTP mode), per-tool scope checks are enforced.
|
|
79
|
+
*/
|
|
80
|
+
export function createMcpServer(options) {
|
|
81
|
+
const { authInfo } = options ?? {};
|
|
82
|
+
const server = new McpServer({
|
|
83
|
+
name: 'orkify',
|
|
84
|
+
version: '1.0.0',
|
|
85
|
+
});
|
|
86
|
+
// Tool: list - List all managed processes
|
|
87
|
+
server.tool('list', 'List all managed processes with their status, workers, memory, and CPU usage', {}, async () => {
|
|
88
|
+
const access = checkToolAccess('list', authInfo);
|
|
89
|
+
if (!access.allowed)
|
|
90
|
+
return access.error;
|
|
91
|
+
try {
|
|
92
|
+
const response = await mcpDaemonClient.request(IPCMessageType.LIST);
|
|
93
|
+
if (response.success) {
|
|
94
|
+
const processes = response.data;
|
|
95
|
+
if (processes.length === 0) {
|
|
96
|
+
return formatSuccess({ processes: [], message: 'No processes running' });
|
|
97
|
+
}
|
|
98
|
+
return formatSuccess(processes);
|
|
99
|
+
}
|
|
100
|
+
return formatError(response.error || 'Failed to list processes', 'LIST_FAILED');
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
return formatError(err.message, 'CONNECTION_ERROR');
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
// Tool: up - Start a new process
|
|
107
|
+
server.tool('up', 'Start a new managed process in daemon mode', {
|
|
108
|
+
script: z.string().describe('Path to the script file to run'),
|
|
109
|
+
name: z.string().optional().describe('Process name (defaults to script basename)'),
|
|
110
|
+
workers: z
|
|
111
|
+
.number()
|
|
112
|
+
.optional()
|
|
113
|
+
.describe('Number of workers (0 = CPU cores, negative = CPUs minus value)'),
|
|
114
|
+
watch: z.boolean().optional().describe('Watch for file changes and auto-reload'),
|
|
115
|
+
cwd: z.string().optional().describe('Working directory for the process'),
|
|
116
|
+
sticky: z.boolean().optional().describe('Enable sticky sessions for Socket.IO/WebSocket'),
|
|
117
|
+
port: z
|
|
118
|
+
.number()
|
|
119
|
+
.optional()
|
|
120
|
+
.describe('Port for sticky session routing (required with sticky)'),
|
|
121
|
+
nodeArgs: z
|
|
122
|
+
.array(z.string())
|
|
123
|
+
.optional()
|
|
124
|
+
.describe('Node.js arguments (e.g., ["--inspect", "--max-old-space-size=4096"])'),
|
|
125
|
+
args: z.array(z.string()).optional().describe('Script arguments'),
|
|
126
|
+
healthCheck: z
|
|
127
|
+
.string()
|
|
128
|
+
.optional()
|
|
129
|
+
.describe('Health check endpoint path (e.g. /health). Requires port to be set.'),
|
|
130
|
+
}, async (params) => {
|
|
131
|
+
const access = checkToolAccess('up', authInfo);
|
|
132
|
+
if (!access.allowed)
|
|
133
|
+
return access.error;
|
|
134
|
+
try {
|
|
135
|
+
const cwd = params.cwd || process.cwd();
|
|
136
|
+
const payload = {
|
|
137
|
+
script: resolve(cwd, params.script),
|
|
138
|
+
name: params.name,
|
|
139
|
+
workers: parseWorkers(params.workers),
|
|
140
|
+
watch: params.watch || false,
|
|
141
|
+
cwd,
|
|
142
|
+
sticky: params.sticky || false,
|
|
143
|
+
port: params.port,
|
|
144
|
+
nodeArgs: params.nodeArgs || [],
|
|
145
|
+
args: params.args || [],
|
|
146
|
+
healthCheck: params.healthCheck,
|
|
147
|
+
};
|
|
148
|
+
const response = await mcpDaemonClient.request(IPCMessageType.UP, payload);
|
|
149
|
+
if (response.success) {
|
|
150
|
+
const info = response.data;
|
|
151
|
+
return formatSuccess({
|
|
152
|
+
message: `Process "${info.name}" started successfully`,
|
|
153
|
+
process: info,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return formatError(response.error || 'Failed to start process', 'START_FAILED');
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
return formatError(err.message, 'CONNECTION_ERROR');
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
// Tool: down - Stop process(es)
|
|
163
|
+
server.tool('down', 'Stop a running process by name, numeric ID, or "all" to stop all processes', {
|
|
164
|
+
target: z.string().describe('Process name, numeric ID, or "all"'),
|
|
165
|
+
}, async (params) => {
|
|
166
|
+
const access = checkToolAccess('down', authInfo);
|
|
167
|
+
if (!access.allowed)
|
|
168
|
+
return access.error;
|
|
169
|
+
try {
|
|
170
|
+
const payload = { target: params.target };
|
|
171
|
+
const response = await mcpDaemonClient.request(IPCMessageType.DOWN, payload);
|
|
172
|
+
if (response.success) {
|
|
173
|
+
return formatSuccess({
|
|
174
|
+
message: `Successfully stopped: ${params.target}`,
|
|
175
|
+
data: response.data,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return formatError(response.error || 'Failed to stop process', 'STOP_FAILED', {
|
|
179
|
+
target: params.target,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
return formatError(err.message, 'CONNECTION_ERROR');
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
// Tool: restart - Hard restart (stop + start)
|
|
187
|
+
server.tool('restart', 'Hard restart a process (stops then starts). For zero-downtime, use reload instead.', {
|
|
188
|
+
target: z.string().describe('Process name, numeric ID, or "all"'),
|
|
189
|
+
}, async (params) => {
|
|
190
|
+
const access = checkToolAccess('restart', authInfo);
|
|
191
|
+
if (!access.allowed)
|
|
192
|
+
return access.error;
|
|
193
|
+
try {
|
|
194
|
+
const payload = { target: params.target };
|
|
195
|
+
const response = await mcpDaemonClient.request(IPCMessageType.RESTART, payload);
|
|
196
|
+
if (response.success) {
|
|
197
|
+
return formatSuccess({
|
|
198
|
+
message: `Successfully restarted: ${params.target}`,
|
|
199
|
+
data: response.data,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return formatError(response.error || 'Failed to restart process', 'RESTART_FAILED', {
|
|
203
|
+
target: params.target,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
return formatError(err.message, 'CONNECTION_ERROR');
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
// Tool: reload - Zero-downtime rolling reload
|
|
211
|
+
server.tool('reload', 'Zero-downtime rolling reload for cluster mode processes. Spawns new workers before killing old ones.', {
|
|
212
|
+
target: z.string().describe('Process name, numeric ID, or "all"'),
|
|
213
|
+
}, async (params) => {
|
|
214
|
+
const access = checkToolAccess('reload', authInfo);
|
|
215
|
+
if (!access.allowed)
|
|
216
|
+
return access.error;
|
|
217
|
+
try {
|
|
218
|
+
const payload = { target: params.target };
|
|
219
|
+
const response = await mcpDaemonClient.request(IPCMessageType.RELOAD, payload);
|
|
220
|
+
if (response.success) {
|
|
221
|
+
return formatSuccess({
|
|
222
|
+
message: `Successfully reloaded: ${params.target}`,
|
|
223
|
+
data: response.data,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return formatError(response.error || 'Failed to reload process', 'RELOAD_FAILED', {
|
|
227
|
+
target: params.target,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
return formatError(err.message, 'CONNECTION_ERROR');
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
// Tool: delete - Stop and remove from list
|
|
235
|
+
server.tool('delete', 'Stop and remove a process from the managed list', {
|
|
236
|
+
target: z.string().describe('Process name, numeric ID, or "all"'),
|
|
237
|
+
}, async (params) => {
|
|
238
|
+
const access = checkToolAccess('delete', authInfo);
|
|
239
|
+
if (!access.allowed)
|
|
240
|
+
return access.error;
|
|
241
|
+
try {
|
|
242
|
+
const payload = { target: params.target };
|
|
243
|
+
const response = await mcpDaemonClient.request(IPCMessageType.DELETE, payload);
|
|
244
|
+
if (response.success) {
|
|
245
|
+
return formatSuccess({
|
|
246
|
+
message: `Successfully deleted: ${params.target}`,
|
|
247
|
+
data: response.data,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return formatError(response.error || 'Failed to delete process', 'DELETE_FAILED', {
|
|
251
|
+
target: params.target,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
return formatError(err.message, 'CONNECTION_ERROR');
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
// Tool: logs - Get recent log lines
|
|
259
|
+
server.tool('logs', 'Get recent log lines from a process or all processes', {
|
|
260
|
+
target: z.string().optional().describe('Process name or ID (optional, defaults to all)'),
|
|
261
|
+
lines: z.number().optional().describe('Number of lines to retrieve (default: 100)'),
|
|
262
|
+
}, async (params) => {
|
|
263
|
+
const access = checkToolAccess('logs', authInfo);
|
|
264
|
+
if (!access.allowed)
|
|
265
|
+
return access.error;
|
|
266
|
+
try {
|
|
267
|
+
const payload = {
|
|
268
|
+
target: params.target,
|
|
269
|
+
lines: params.lines || 100,
|
|
270
|
+
follow: false,
|
|
271
|
+
};
|
|
272
|
+
const response = await mcpDaemonClient.request(IPCMessageType.LOGS, payload);
|
|
273
|
+
if (response.success) {
|
|
274
|
+
const data = response.data;
|
|
275
|
+
if (data.logs && data.logs.length > 0) {
|
|
276
|
+
const output = data.logs.map((f) => f.lines.join('\n')).join('\n');
|
|
277
|
+
return formatSuccess(output);
|
|
278
|
+
}
|
|
279
|
+
return formatSuccess({ message: 'No logs found' });
|
|
280
|
+
}
|
|
281
|
+
return formatError(response.error || 'Failed to get logs', 'LOGS_FAILED', {
|
|
282
|
+
target: params.target,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
return formatError(err.message, 'CONNECTION_ERROR');
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
// Tool: snap - Snapshot process list to disk
|
|
290
|
+
server.tool('snap', 'Snapshot the current process list to disk for later restoration with restore', {
|
|
291
|
+
noEnv: z
|
|
292
|
+
.boolean()
|
|
293
|
+
.optional()
|
|
294
|
+
.describe('Do not save environment variables in snapshot file. Processes restored via restore will inherit the daemon environment instead.'),
|
|
295
|
+
file: z
|
|
296
|
+
.string()
|
|
297
|
+
.optional()
|
|
298
|
+
.describe('Path to snapshot file (default: ~/.orkify/snapshot.yml)'),
|
|
299
|
+
}, async (params) => {
|
|
300
|
+
const access = checkToolAccess('snap', authInfo);
|
|
301
|
+
if (!access.allowed)
|
|
302
|
+
return access.error;
|
|
303
|
+
try {
|
|
304
|
+
const payload = { noEnv: params.noEnv, file: params.file };
|
|
305
|
+
const response = await mcpDaemonClient.request(IPCMessageType.SNAP, payload);
|
|
306
|
+
if (response.success) {
|
|
307
|
+
return formatSuccess({
|
|
308
|
+
message: 'Snapshot saved successfully',
|
|
309
|
+
data: response.data,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
return formatError(response.error || 'Failed to save snapshot', 'SNAP_FAILED');
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
return formatError(err.message, 'CONNECTION_ERROR');
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
// Tool: restore - Restore saved processes
|
|
319
|
+
server.tool('restore', 'Restore previously saved processes from a snapshot file', {
|
|
320
|
+
file: z
|
|
321
|
+
.string()
|
|
322
|
+
.optional()
|
|
323
|
+
.describe('Path to snapshot file (default: ~/.orkify/snapshot.yml)'),
|
|
324
|
+
}, async (params) => {
|
|
325
|
+
const access = checkToolAccess('restore', authInfo);
|
|
326
|
+
if (!access.allowed)
|
|
327
|
+
return access.error;
|
|
328
|
+
try {
|
|
329
|
+
const payload = { file: params.file };
|
|
330
|
+
const response = await mcpDaemonClient.request(IPCMessageType.RESTORE, payload);
|
|
331
|
+
if (response.success) {
|
|
332
|
+
return formatSuccess({
|
|
333
|
+
message: 'Processes restored successfully',
|
|
334
|
+
data: response.data,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
return formatError(response.error || 'Failed to restore processes', 'RESTORE_FAILED');
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
return formatError(err.message, 'CONNECTION_ERROR');
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
// Tool: listAllUsers - List processes from all users on the system
|
|
344
|
+
server.tool('listAllUsers', 'List processes from all users on the system. Requires elevated privileges (sudo) on Unix.', {}, async () => {
|
|
345
|
+
const access = checkToolAccess('listAllUsers', authInfo);
|
|
346
|
+
if (!access.allowed)
|
|
347
|
+
return access.error;
|
|
348
|
+
try {
|
|
349
|
+
// Check for elevated privileges on Unix
|
|
350
|
+
if (process.platform !== 'win32' && !isElevated()) {
|
|
351
|
+
return formatError('This command requires elevated privileges. Run the MCP server with sudo.', 'ELEVATION_REQUIRED');
|
|
352
|
+
}
|
|
353
|
+
const result = await listAllUsers();
|
|
354
|
+
const response = {
|
|
355
|
+
users: result.users,
|
|
356
|
+
warnings: result.warnings,
|
|
357
|
+
inaccessibleUsers: result.inaccessibleUsers,
|
|
358
|
+
totalProcesses: result.users.reduce((sum, u) => sum + u.processes.length, 0),
|
|
359
|
+
};
|
|
360
|
+
if (result.inaccessibleUsers.length > 0) {
|
|
361
|
+
return formatSuccess({
|
|
362
|
+
...response,
|
|
363
|
+
incomplete: true,
|
|
364
|
+
error: `Could not access some users' processes: ${result.inaccessibleUsers.join(', ')}`,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
if (result.users.length === 0) {
|
|
368
|
+
return formatSuccess({
|
|
369
|
+
...response,
|
|
370
|
+
message: 'No orkify processes running on this system.',
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
return formatSuccess(response);
|
|
374
|
+
}
|
|
375
|
+
catch (err) {
|
|
376
|
+
return formatError(err.message, 'LIST_ALL_USERS_FAILED');
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
// Tool: kill - Stop the daemon
|
|
380
|
+
server.tool('kill', 'Stop the ORKIFY daemon process. All managed processes will be stopped.', {}, async () => {
|
|
381
|
+
const access = checkToolAccess('kill', authInfo);
|
|
382
|
+
if (!access.allowed)
|
|
383
|
+
return access.error;
|
|
384
|
+
try {
|
|
385
|
+
const response = await mcpDaemonClient.request(IPCMessageType.KILL_DAEMON);
|
|
386
|
+
if (response.success) {
|
|
387
|
+
return formatSuccess({
|
|
388
|
+
message: 'Daemon stopped successfully',
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
return formatError(response.error || 'Failed to stop daemon', 'KILL_FAILED');
|
|
392
|
+
}
|
|
393
|
+
catch (err) {
|
|
394
|
+
// Connection error is expected when daemon shuts down
|
|
395
|
+
if (err.message.includes('Connection closed')) {
|
|
396
|
+
return formatSuccess({
|
|
397
|
+
message: 'Daemon stopped successfully',
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
return formatError(err.message, 'CONNECTION_ERROR');
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
return server;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Start the MCP server with stdio transport
|
|
407
|
+
*/
|
|
408
|
+
export async function startMcpServer() {
|
|
409
|
+
const server = createMcpServer();
|
|
410
|
+
const transport = new StdioServerTransport();
|
|
411
|
+
await server.connect(transport);
|
|
412
|
+
}
|
|
413
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse the function name from a V8 stack trace for a specific file and line.
|
|
3
|
+
* Returns null if the frame has no function name (bare path) or isn't found.
|
|
4
|
+
*/
|
|
5
|
+
export declare function parseFunctionName(stack: string, targetFile: string, targetLine: number): null | string;
|
|
6
|
+
/**
|
|
7
|
+
* Normalize an error message by replacing dynamic values with `*`.
|
|
8
|
+
* This stabilizes fingerprints across instances of the same logical error.
|
|
9
|
+
*/
|
|
10
|
+
export declare function normalizeMessage(message: string): string;
|
|
11
|
+
interface FingerprintInput {
|
|
12
|
+
errorName: string;
|
|
13
|
+
message: string;
|
|
14
|
+
file: string;
|
|
15
|
+
line: number;
|
|
16
|
+
functionName?: null | string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Compute a stable error fingerprint.
|
|
20
|
+
*
|
|
21
|
+
* Uses file + function name when available (stable across line shifts).
|
|
22
|
+
* Falls back to file + line number when no function name is available.
|
|
23
|
+
* Includes error name and normalized message for proper grouping.
|
|
24
|
+
*/
|
|
25
|
+
export declare function computeFingerprint(input: FingerprintInput): string;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=compute-fingerprint.d.ts.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Parse the function name from a V8 stack trace for a specific file and line.
|
|
4
|
+
* Returns null if the frame has no function name (bare path) or isn't found.
|
|
5
|
+
*/
|
|
6
|
+
export function parseFunctionName(stack, targetFile, targetLine) {
|
|
7
|
+
if (!stack || !targetFile)
|
|
8
|
+
return null;
|
|
9
|
+
const needle = targetFile + ':' + targetLine + ':';
|
|
10
|
+
// Also match file:// URL form (ESM stacks keep the URL in the raw trace)
|
|
11
|
+
const fileUrlNeedle = targetFile.startsWith('/')
|
|
12
|
+
? 'file://' + targetFile + ':' + targetLine + ':'
|
|
13
|
+
: null;
|
|
14
|
+
for (const line of stack.split('\n')) {
|
|
15
|
+
// Check this line references the target file and line number
|
|
16
|
+
if (!line.includes(needle) && !(fileUrlNeedle && line.includes(fileUrlNeedle)))
|
|
17
|
+
continue;
|
|
18
|
+
// Must be an actual stack frame line (starts with "at")
|
|
19
|
+
const m = line.match(/^\s*at\s+(.+?)\s+[(]/);
|
|
20
|
+
if (!m)
|
|
21
|
+
continue; // Not a frame line or bare path — keep searching
|
|
22
|
+
let name = m[1];
|
|
23
|
+
// Strip "async " prefix
|
|
24
|
+
if (name.startsWith('async ')) {
|
|
25
|
+
name = name.slice(6);
|
|
26
|
+
}
|
|
27
|
+
// "Object.<anonymous>" → "<anonymous>"
|
|
28
|
+
if (name === 'Object.<anonymous>') {
|
|
29
|
+
return '<anonymous>';
|
|
30
|
+
}
|
|
31
|
+
return name;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
|
|
36
|
+
const HEX_RE = /\b[0-9a-f]{16,}\b/gi;
|
|
37
|
+
const IPV4_RE = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g;
|
|
38
|
+
const NUMBER_RE = /\b\d+\b/g;
|
|
39
|
+
/**
|
|
40
|
+
* Normalize an error message by replacing dynamic values with `*`.
|
|
41
|
+
* This stabilizes fingerprints across instances of the same logical error.
|
|
42
|
+
*/
|
|
43
|
+
export function normalizeMessage(message) {
|
|
44
|
+
return message
|
|
45
|
+
.replace(UUID_RE, '*')
|
|
46
|
+
.replace(HEX_RE, '*')
|
|
47
|
+
.replace(IPV4_RE, '*')
|
|
48
|
+
.replace(NUMBER_RE, '*');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Compute a stable error fingerprint.
|
|
52
|
+
*
|
|
53
|
+
* Uses file + function name when available (stable across line shifts).
|
|
54
|
+
* Falls back to file + line number when no function name is available.
|
|
55
|
+
* Includes error name and normalized message for proper grouping.
|
|
56
|
+
*/
|
|
57
|
+
export function computeFingerprint(input) {
|
|
58
|
+
const normalized = normalizeMessage(input.message);
|
|
59
|
+
const location = input.functionName
|
|
60
|
+
? input.file + '\0' + input.functionName
|
|
61
|
+
: input.file + ':' + input.line;
|
|
62
|
+
const raw = input.errorName + '\0' + normalized + '\0' + location;
|
|
63
|
+
return createHash('sha256').update(raw).digest('hex').slice(0, 32);
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=compute-fingerprint.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse user stack frames from an Error.stack string.
|
|
3
|
+
*
|
|
4
|
+
* Handles multiple file path formats found in stack traces:
|
|
5
|
+
* - Absolute paths: /home/user/project/src/app.ts
|
|
6
|
+
* - Windows paths: C:\Users\project\src\app.ts
|
|
7
|
+
* - ESM file:// URLs: file:///home/user/project/src/app.ts
|
|
8
|
+
* - webpack-internal:// (Next.js): webpack-internal:///(rsc)/./src/app.ts
|
|
9
|
+
* - webpack:// (plain webpack): webpack:///./src/file.ts
|
|
10
|
+
* - Unknown URL schemes (data:, blob:, turbopack://, etc.) are skipped.
|
|
11
|
+
*
|
|
12
|
+
* IMPORTANT: The probe string in constants.ts inlines a copy of this logic
|
|
13
|
+
* (it runs inside a data: URL and cannot import modules). Keep them in sync.
|
|
14
|
+
*/
|
|
15
|
+
export type { StackFrame } from '@orkify/next/utils';
|
|
16
|
+
export declare function parseUserFrames(stack: string, cwd?: string): Array<{
|
|
17
|
+
file: string;
|
|
18
|
+
line: number;
|
|
19
|
+
column: number;
|
|
20
|
+
}>;
|
|
21
|
+
//# sourceMappingURL=parse-frames.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse user stack frames from an Error.stack string.
|
|
3
|
+
*
|
|
4
|
+
* Handles multiple file path formats found in stack traces:
|
|
5
|
+
* - Absolute paths: /home/user/project/src/app.ts
|
|
6
|
+
* - Windows paths: C:\Users\project\src\app.ts
|
|
7
|
+
* - ESM file:// URLs: file:///home/user/project/src/app.ts
|
|
8
|
+
* - webpack-internal:// (Next.js): webpack-internal:///(rsc)/./src/app.ts
|
|
9
|
+
* - webpack:// (plain webpack): webpack:///./src/file.ts
|
|
10
|
+
* - Unknown URL schemes (data:, blob:, turbopack://, etc.) are skipped.
|
|
11
|
+
*
|
|
12
|
+
* IMPORTANT: The probe string in constants.ts inlines a copy of this logic
|
|
13
|
+
* (it runs inside a data: URL and cannot import modules). Keep them in sync.
|
|
14
|
+
*/
|
|
15
|
+
import { resolve } from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
const STACK_LINE_RE = /at\s+(?:.*?\s+)?\(?(.+?):(\d+):(\d+)\)?$/;
|
|
18
|
+
const WEBPACK_PREFIX_RE = /^webpack(?:-internal)?:\/\/\/(?:[^/]*\/)?/;
|
|
19
|
+
const URL_SCHEME_RE = /^[a-z][a-z0-9+.-]+:/i;
|
|
20
|
+
export function parseUserFrames(stack, cwd = process.cwd()) {
|
|
21
|
+
const frames = [];
|
|
22
|
+
if (!stack)
|
|
23
|
+
return frames;
|
|
24
|
+
for (const line of stack.split('\n')) {
|
|
25
|
+
const m = line.match(STACK_LINE_RE);
|
|
26
|
+
if (!m)
|
|
27
|
+
continue;
|
|
28
|
+
let file = m[1];
|
|
29
|
+
// ESM file:// URLs → absolute path
|
|
30
|
+
if (file.startsWith('file://')) {
|
|
31
|
+
try {
|
|
32
|
+
file = fileURLToPath(file);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// webpack / webpack-internal URLs → resolve relative path against cwd
|
|
39
|
+
else if (file.startsWith('webpack-internal://') || file.startsWith('webpack://')) {
|
|
40
|
+
const relative = file.replace(WEBPACK_PREFIX_RE, '');
|
|
41
|
+
file = resolve(cwd, relative);
|
|
42
|
+
}
|
|
43
|
+
// Any other URL scheme (data:, blob:, turbopack://, http://, etc.) → skip
|
|
44
|
+
else if (URL_SCHEME_RE.test(file)) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
// Filter out Node.js internals and dependencies
|
|
48
|
+
if (file.startsWith('node:') || file.includes('node_modules')) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
frames.push({ file, line: parseInt(m[2], 10), column: parseInt(m[3], 10) });
|
|
52
|
+
if (frames.length >= 10)
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
return frames;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=parse-frames.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SourceContextFrame } from '../types/index.js';
|
|
2
|
+
interface TopFrame {
|
|
3
|
+
file: string;
|
|
4
|
+
line: number;
|
|
5
|
+
column: number;
|
|
6
|
+
}
|
|
7
|
+
interface ResolveResult {
|
|
8
|
+
sourceContext: null | SourceContextFrame[];
|
|
9
|
+
topFrame: null | TopFrame;
|
|
10
|
+
resolvedFunctionName: null | string;
|
|
11
|
+
resolved: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolve source maps for all frames in an error's source context.
|
|
15
|
+
*
|
|
16
|
+
* For each frame, attempts to find and parse the corresponding .map file,
|
|
17
|
+
* then replaces the minified location with the original source location.
|
|
18
|
+
* Frames that can't be resolved are kept as-is.
|
|
19
|
+
*
|
|
20
|
+
* Returns the resolved function name from the source map (if available)
|
|
21
|
+
* so the caller can use it for fingerprinting.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveSourceMaps(sourceContext: null | SourceContextFrame[], topFrame: null | TopFrame): ResolveResult;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=resolve-sourcemaps.d.ts.map
|