@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.
Files changed (203) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +1701 -0
  3. package/bin/orkify +3 -0
  4. package/boot/systemd/orkify@.service +30 -0
  5. package/dist/agent-name.d.ts +4 -0
  6. package/dist/agent-name.js +42 -0
  7. package/dist/alerts/AlertEvaluator.d.ts +14 -0
  8. package/dist/alerts/AlertEvaluator.js +135 -0
  9. package/dist/cli/commands/autostart.d.ts +3 -0
  10. package/dist/cli/commands/autostart.js +11 -0
  11. package/dist/cli/commands/crash-test.d.ts +3 -0
  12. package/dist/cli/commands/crash-test.js +17 -0
  13. package/dist/cli/commands/daemon-reload.d.ts +3 -0
  14. package/dist/cli/commands/daemon-reload.js +72 -0
  15. package/dist/cli/commands/delete.d.ts +3 -0
  16. package/dist/cli/commands/delete.js +37 -0
  17. package/dist/cli/commands/deploy.d.ts +6 -0
  18. package/dist/cli/commands/deploy.js +266 -0
  19. package/dist/cli/commands/down.d.ts +3 -0
  20. package/dist/cli/commands/down.js +36 -0
  21. package/dist/cli/commands/flush.d.ts +3 -0
  22. package/dist/cli/commands/flush.js +28 -0
  23. package/dist/cli/commands/kill.d.ts +3 -0
  24. package/dist/cli/commands/kill.js +35 -0
  25. package/dist/cli/commands/list.d.ts +14 -0
  26. package/dist/cli/commands/list.js +361 -0
  27. package/dist/cli/commands/logs.d.ts +3 -0
  28. package/dist/cli/commands/logs.js +107 -0
  29. package/dist/cli/commands/mcp.d.ts +3 -0
  30. package/dist/cli/commands/mcp.js +151 -0
  31. package/dist/cli/commands/reload.d.ts +3 -0
  32. package/dist/cli/commands/reload.js +54 -0
  33. package/dist/cli/commands/restart.d.ts +3 -0
  34. package/dist/cli/commands/restart.js +43 -0
  35. package/dist/cli/commands/restore.d.ts +3 -0
  36. package/dist/cli/commands/restore.js +88 -0
  37. package/dist/cli/commands/run.d.ts +8 -0
  38. package/dist/cli/commands/run.js +212 -0
  39. package/dist/cli/commands/snap.d.ts +3 -0
  40. package/dist/cli/commands/snap.js +30 -0
  41. package/dist/cli/commands/up.d.ts +3 -0
  42. package/dist/cli/commands/up.js +125 -0
  43. package/dist/cli/crash-recovery.d.ts +2 -0
  44. package/dist/cli/crash-recovery.js +67 -0
  45. package/dist/cli/index.d.ts +3 -0
  46. package/dist/cli/index.js +46 -0
  47. package/dist/cli/parse.d.ts +28 -0
  48. package/dist/cli/parse.js +97 -0
  49. package/dist/cluster/ClusterWrapper.d.ts +18 -0
  50. package/dist/cluster/ClusterWrapper.js +602 -0
  51. package/dist/config/ConfigStore.d.ts +11 -0
  52. package/dist/config/ConfigStore.js +21 -0
  53. package/dist/config/schema.d.ts +103 -0
  54. package/dist/config/schema.js +49 -0
  55. package/dist/constants.d.ts +83 -0
  56. package/dist/constants.js +289 -0
  57. package/dist/cron/CronScheduler.d.ts +25 -0
  58. package/dist/cron/CronScheduler.js +149 -0
  59. package/dist/daemon/GracefulManager.d.ts +8 -0
  60. package/dist/daemon/GracefulManager.js +29 -0
  61. package/dist/daemon/ManagedProcess.d.ts +71 -0
  62. package/dist/daemon/ManagedProcess.js +1020 -0
  63. package/dist/daemon/Orchestrator.d.ts +51 -0
  64. package/dist/daemon/Orchestrator.js +416 -0
  65. package/dist/daemon/RotatingWriter.d.ts +27 -0
  66. package/dist/daemon/RotatingWriter.js +264 -0
  67. package/dist/daemon/index.d.ts +2 -0
  68. package/dist/daemon/index.js +106 -0
  69. package/dist/daemon/startDaemon.d.ts +30 -0
  70. package/dist/daemon/startDaemon.js +693 -0
  71. package/dist/deploy/CommandPoller.d.ts +13 -0
  72. package/dist/deploy/CommandPoller.js +53 -0
  73. package/dist/deploy/DeployExecutor.d.ts +33 -0
  74. package/dist/deploy/DeployExecutor.js +340 -0
  75. package/dist/deploy/config.d.ts +20 -0
  76. package/dist/deploy/config.js +161 -0
  77. package/dist/deploy/env.d.ts +2 -0
  78. package/dist/deploy/env.js +17 -0
  79. package/dist/deploy/tarball.d.ts +32 -0
  80. package/dist/deploy/tarball.js +243 -0
  81. package/dist/detect/framework.d.ts +2 -0
  82. package/dist/detect/framework.js +24 -0
  83. package/dist/ipc/DaemonClient.d.ts +31 -0
  84. package/dist/ipc/DaemonClient.js +248 -0
  85. package/dist/ipc/DaemonServer.d.ts +28 -0
  86. package/dist/ipc/DaemonServer.js +166 -0
  87. package/dist/ipc/MultiUserClient.d.ts +27 -0
  88. package/dist/ipc/MultiUserClient.js +203 -0
  89. package/dist/ipc/protocol.d.ts +7 -0
  90. package/dist/ipc/protocol.js +53 -0
  91. package/dist/ipc/restoreDaemon.d.ts +8 -0
  92. package/dist/ipc/restoreDaemon.js +19 -0
  93. package/dist/machine-id.d.ts +11 -0
  94. package/dist/machine-id.js +51 -0
  95. package/dist/mcp/auth.d.ts +118 -0
  96. package/dist/mcp/auth.js +245 -0
  97. package/dist/mcp/http.d.ts +20 -0
  98. package/dist/mcp/http.js +229 -0
  99. package/dist/mcp/index.d.ts +3 -0
  100. package/dist/mcp/index.js +8 -0
  101. package/dist/mcp/server.d.ts +37 -0
  102. package/dist/mcp/server.js +413 -0
  103. package/dist/probe/compute-fingerprint.d.ts +27 -0
  104. package/dist/probe/compute-fingerprint.js +65 -0
  105. package/dist/probe/parse-frames.d.ts +21 -0
  106. package/dist/probe/parse-frames.js +57 -0
  107. package/dist/probe/resolve-sourcemaps.d.ts +25 -0
  108. package/dist/probe/resolve-sourcemaps.js +281 -0
  109. package/dist/state/StateStore.d.ts +11 -0
  110. package/dist/state/StateStore.js +78 -0
  111. package/dist/telemetry/TelemetryReporter.d.ts +49 -0
  112. package/dist/telemetry/TelemetryReporter.js +451 -0
  113. package/dist/types/index.d.ts +373 -0
  114. package/dist/types/index.js +2 -0
  115. package/package.json +148 -0
  116. package/packages/cache/README.md +114 -0
  117. package/packages/cache/dist/CacheClient.d.ts +26 -0
  118. package/packages/cache/dist/CacheClient.d.ts.map +1 -0
  119. package/packages/cache/dist/CacheClient.js +174 -0
  120. package/packages/cache/dist/CacheClient.js.map +1 -0
  121. package/packages/cache/dist/CacheFileStore.d.ts +45 -0
  122. package/packages/cache/dist/CacheFileStore.d.ts.map +1 -0
  123. package/packages/cache/dist/CacheFileStore.js +446 -0
  124. package/packages/cache/dist/CacheFileStore.js.map +1 -0
  125. package/packages/cache/dist/CachePersistence.d.ts +9 -0
  126. package/packages/cache/dist/CachePersistence.d.ts.map +1 -0
  127. package/packages/cache/dist/CachePersistence.js +67 -0
  128. package/packages/cache/dist/CachePersistence.js.map +1 -0
  129. package/packages/cache/dist/CachePrimary.d.ts +25 -0
  130. package/packages/cache/dist/CachePrimary.d.ts.map +1 -0
  131. package/packages/cache/dist/CachePrimary.js +155 -0
  132. package/packages/cache/dist/CachePrimary.js.map +1 -0
  133. package/packages/cache/dist/CacheStore.d.ts +50 -0
  134. package/packages/cache/dist/CacheStore.d.ts.map +1 -0
  135. package/packages/cache/dist/CacheStore.js +271 -0
  136. package/packages/cache/dist/CacheStore.js.map +1 -0
  137. package/packages/cache/dist/constants.d.ts +6 -0
  138. package/packages/cache/dist/constants.d.ts.map +1 -0
  139. package/packages/cache/dist/constants.js +9 -0
  140. package/packages/cache/dist/constants.js.map +1 -0
  141. package/packages/cache/dist/index.d.ts +16 -0
  142. package/packages/cache/dist/index.d.ts.map +1 -0
  143. package/packages/cache/dist/index.js +86 -0
  144. package/packages/cache/dist/index.js.map +1 -0
  145. package/packages/cache/dist/serialize.d.ts +9 -0
  146. package/packages/cache/dist/serialize.d.ts.map +1 -0
  147. package/packages/cache/dist/serialize.js +40 -0
  148. package/packages/cache/dist/serialize.js.map +1 -0
  149. package/packages/cache/dist/types.d.ts +123 -0
  150. package/packages/cache/dist/types.d.ts.map +1 -0
  151. package/packages/cache/dist/types.js +2 -0
  152. package/packages/cache/dist/types.js.map +1 -0
  153. package/packages/cache/package.json +27 -0
  154. package/packages/cache/src/CacheClient.ts +227 -0
  155. package/packages/cache/src/CacheFileStore.ts +528 -0
  156. package/packages/cache/src/CachePersistence.ts +89 -0
  157. package/packages/cache/src/CachePrimary.ts +172 -0
  158. package/packages/cache/src/CacheStore.ts +308 -0
  159. package/packages/cache/src/constants.ts +10 -0
  160. package/packages/cache/src/index.ts +100 -0
  161. package/packages/cache/src/serialize.ts +49 -0
  162. package/packages/cache/src/types.ts +156 -0
  163. package/packages/cache/tsconfig.json +18 -0
  164. package/packages/cache/tsconfig.tsbuildinfo +1 -0
  165. package/packages/next/README.md +166 -0
  166. package/packages/next/dist/error-capture.d.ts +34 -0
  167. package/packages/next/dist/error-capture.d.ts.map +1 -0
  168. package/packages/next/dist/error-capture.js +130 -0
  169. package/packages/next/dist/error-capture.js.map +1 -0
  170. package/packages/next/dist/error-handler.d.ts +10 -0
  171. package/packages/next/dist/error-handler.d.ts.map +1 -0
  172. package/packages/next/dist/error-handler.js +186 -0
  173. package/packages/next/dist/error-handler.js.map +1 -0
  174. package/packages/next/dist/isr-cache.d.ts +9 -0
  175. package/packages/next/dist/isr-cache.d.ts.map +1 -0
  176. package/packages/next/dist/isr-cache.js +86 -0
  177. package/packages/next/dist/isr-cache.js.map +1 -0
  178. package/packages/next/dist/stream.d.ts +5 -0
  179. package/packages/next/dist/stream.d.ts.map +1 -0
  180. package/packages/next/dist/stream.js +22 -0
  181. package/packages/next/dist/stream.js.map +1 -0
  182. package/packages/next/dist/types.d.ts +33 -0
  183. package/packages/next/dist/types.d.ts.map +1 -0
  184. package/packages/next/dist/types.js +6 -0
  185. package/packages/next/dist/types.js.map +1 -0
  186. package/packages/next/dist/use-cache.d.ts +4 -0
  187. package/packages/next/dist/use-cache.d.ts.map +1 -0
  188. package/packages/next/dist/use-cache.js +86 -0
  189. package/packages/next/dist/use-cache.js.map +1 -0
  190. package/packages/next/dist/utils.d.ts +32 -0
  191. package/packages/next/dist/utils.d.ts.map +1 -0
  192. package/packages/next/dist/utils.js +88 -0
  193. package/packages/next/dist/utils.js.map +1 -0
  194. package/packages/next/package.json +52 -0
  195. package/packages/next/src/error-capture.ts +177 -0
  196. package/packages/next/src/error-handler.ts +221 -0
  197. package/packages/next/src/isr-cache.ts +100 -0
  198. package/packages/next/src/stream.ts +23 -0
  199. package/packages/next/src/types.ts +33 -0
  200. package/packages/next/src/use-cache.ts +99 -0
  201. package/packages/next/src/utils.ts +102 -0
  202. package/packages/next/tsconfig.json +19 -0
  203. 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