@moda-ai/cli 1.0.4 → 1.1.0

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 (45) hide show
  1. package/.env.example +2 -2
  2. package/README.md +52 -50
  3. package/dist/auth.js +39 -67
  4. package/dist/cli.d.ts +1 -0
  5. package/dist/cli.js +140 -14
  6. package/dist/config.d.ts +1 -2
  7. package/dist/config.js +3 -4
  8. package/dist/init/detect.d.ts +1 -1
  9. package/dist/init/detect.js +28 -5
  10. package/dist/init/index.js +26 -68
  11. package/dist/init/mcp.d.ts +1 -7
  12. package/dist/init/mcp.js +5 -88
  13. package/dist/init/rules.d.ts +1 -1
  14. package/dist/init/rules.js +151 -58
  15. package/dist/init/tenant.js +4 -42
  16. package/dist/schemas.d.ts +71 -0
  17. package/dist/schemas.js +44 -0
  18. package/dist/transforms.d.ts +25 -0
  19. package/dist/transforms.js +102 -0
  20. package/package.json +8 -19
  21. package/skills/integrate-moda/SKILL.md +63 -0
  22. package/skills/moda-cli/SKILL.md +323 -0
  23. package/dist/__tests__/api-client.test.d.ts +0 -2
  24. package/dist/__tests__/api-client.test.js +0 -73
  25. package/dist/__tests__/cli.test.d.ts +0 -2
  26. package/dist/__tests__/cli.test.js +0 -109
  27. package/dist/__tests__/e2e.test.d.ts +0 -2
  28. package/dist/__tests__/e2e.test.js +0 -239
  29. package/dist/__tests__/handlers.test.d.ts +0 -2
  30. package/dist/__tests__/handlers.test.js +0 -71
  31. package/dist/__tests__/integration.test.d.ts +0 -2
  32. package/dist/__tests__/integration.test.js +0 -114
  33. package/dist/__tests__/schemas.test.d.ts +0 -2
  34. package/dist/__tests__/schemas.test.js +0 -81
  35. package/dist/handlers.d.ts +0 -7
  36. package/dist/handlers.js +0 -117
  37. package/dist/http-server.d.ts +0 -3
  38. package/dist/http-server.js +0 -74
  39. package/dist/index.d.ts +0 -3
  40. package/dist/index.js +0 -26
  41. package/dist/tools.d.ts +0 -260
  42. package/dist/tools.js +0 -133
  43. package/dist/worker.d.ts +0 -9
  44. package/dist/worker.js +0 -24
  45. package/mcp-config.example.json +0 -12
@@ -1,81 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { OverviewSchema, ClustersSchema, ClusterConversationsSchema, ConversationsSchema, ContextSchema, FrustrationsSchema, ToolFailuresSchema, ToolFailureDetailSchema, } from '../schemas.js';
3
- describe('OverviewSchema', () => {
4
- it('accepts valid days_back', () => {
5
- expect(OverviewSchema.parse({ days_back: 30 })).toEqual({ days_back: 30 });
6
- });
7
- it('accepts empty object with defaults', () => {
8
- expect(OverviewSchema.parse({})).toEqual({});
9
- });
10
- it('rejects days_back > 90', () => {
11
- expect(() => OverviewSchema.parse({ days_back: 91 })).toThrow();
12
- });
13
- it('rejects days_back < 1', () => {
14
- expect(() => OverviewSchema.parse({ days_back: 0 })).toThrow();
15
- });
16
- });
17
- describe('ClustersSchema', () => {
18
- it('accepts all backend time_range values', () => {
19
- for (const val of ['all', '1h', '3d', '7d', '24h', '30d', '90d']) {
20
- expect(ClustersSchema.parse({ time_range: val }).time_range).toBe(val);
21
- }
22
- });
23
- it('rejects invalid time_range', () => {
24
- expect(() => ClustersSchema.parse({ time_range: '2d' })).toThrow();
25
- });
26
- });
27
- describe('ConversationsSchema', () => {
28
- it('accepts 3d time_range (previously missing)', () => {
29
- expect(ConversationsSchema.parse({ time_range: '3d' }).time_range).toBe('3d');
30
- });
31
- it('accepts all valid time_range values', () => {
32
- for (const val of ['all', '1h', '3d', '24h', '7d', '30d', '90d']) {
33
- expect(ConversationsSchema.parse({ time_range: val }).time_range).toBe(val);
34
- }
35
- });
36
- });
37
- describe('FrustrationsSchema', () => {
38
- it('accepts limit up to 20', () => {
39
- expect(FrustrationsSchema.parse({ limit: 20 }).limit).toBe(20);
40
- });
41
- it('rejects limit > 20 (previously allowed 100)', () => {
42
- expect(() => FrustrationsSchema.parse({ limit: 21 })).toThrow();
43
- });
44
- });
45
- describe('ToolFailureDetailSchema', () => {
46
- it('accepts limit up to 20', () => {
47
- expect(ToolFailureDetailSchema.parse({ tool_name: 'search', limit: 20 }).limit).toBe(20);
48
- });
49
- it('rejects limit > 20 (previously allowed 100)', () => {
50
- expect(() => ToolFailureDetailSchema.parse({ tool_name: 'search', limit: 21 })).toThrow();
51
- });
52
- it('requires tool_name', () => {
53
- expect(() => ToolFailureDetailSchema.parse({})).toThrow();
54
- });
55
- });
56
- describe('ClusterConversationsSchema', () => {
57
- it('requires node_id', () => {
58
- expect(() => ClusterConversationsSchema.parse({})).toThrow();
59
- });
60
- it('accepts valid input', () => {
61
- const result = ClusterConversationsSchema.parse({ node_id: 'cluster-1', limit: 10, offset: 0 });
62
- expect(result.node_id).toBe('cluster-1');
63
- });
64
- });
65
- describe('ContextSchema', () => {
66
- it('requires conversation_id', () => {
67
- expect(() => ContextSchema.parse({})).toThrow();
68
- });
69
- it('accepts msg_index of 0', () => {
70
- expect(ContextSchema.parse({ conversation_id: 'conv-1', msg_index: 0 }).msg_index).toBe(0);
71
- });
72
- it('rejects window > 5', () => {
73
- expect(() => ContextSchema.parse({ conversation_id: 'conv-1', window: 6 })).toThrow();
74
- });
75
- });
76
- describe('ToolFailuresSchema', () => {
77
- it('accepts valid days_back', () => {
78
- expect(ToolFailuresSchema.parse({ days_back: 14 }).days_back).toBe(14);
79
- });
80
- });
81
- //# sourceMappingURL=schemas.test.js.map
@@ -1,7 +0,0 @@
1
- export declare function handleToolCall(name: string, args: unknown): Promise<{
2
- content: {
3
- type: "text";
4
- text: string;
5
- }[];
6
- }>;
7
- //# sourceMappingURL=handlers.d.ts.map
package/dist/handlers.js DELETED
@@ -1,117 +0,0 @@
1
- // src/handlers.ts
2
- import { z } from 'zod';
3
- import { callDataAPI } from './api-client.js';
4
- import { OverviewSchema, ClustersSchema, ClusterConversationsSchema, ConversationsSchema, ContextSchema, FrustrationsSchema, ToolFailuresSchema, ToolFailureDetailSchema, } from './schemas.js';
5
- export async function handleToolCall(name, args) {
6
- try {
7
- switch (name) {
8
- case 'moda_get_overview': {
9
- const params = OverviewSchema.parse(args);
10
- const queryString = params.days_back ? `?days_back=${params.days_back}` : '';
11
- const data = await callDataAPI(`/overview${queryString}`);
12
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
13
- }
14
- case 'moda_get_clusters': {
15
- const params = ClustersSchema.parse(args);
16
- const query = new URLSearchParams();
17
- if (params.parent_id)
18
- query.set('parent_id', params.parent_id);
19
- if (params.time_range)
20
- query.set('time_range', params.time_range);
21
- const queryString = query.toString() ? `?${query.toString()}` : '';
22
- const data = await callDataAPI(`/clusters${queryString}`);
23
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
24
- }
25
- case 'moda_get_cluster_conversations': {
26
- const params = ClusterConversationsSchema.parse(args);
27
- const query = new URLSearchParams();
28
- if (params.limit)
29
- query.set('limit', params.limit.toString());
30
- // FIX: use !== undefined instead of falsy check so offset=0 is included
31
- if (params.offset !== undefined)
32
- query.set('offset', params.offset.toString());
33
- const queryString = query.toString() ? `?${query.toString()}` : '';
34
- const data = await callDataAPI(`/clusters/${params.node_id}/conversations${queryString}`);
35
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
36
- }
37
- case 'moda_search_conversations': {
38
- const params = ConversationsSchema.parse(args);
39
- const query = new URLSearchParams();
40
- if (params.search)
41
- query.set('search', params.search);
42
- if (params.cluster_id)
43
- query.set('cluster_id', params.cluster_id);
44
- if (params.user_id)
45
- query.set('user_id', params.user_id);
46
- if (params.time_range)
47
- query.set('time_range', params.time_range);
48
- if (params.environment)
49
- query.set('environment', params.environment);
50
- if (params.limit)
51
- query.set('limit', params.limit.toString());
52
- // FIX: use !== undefined instead of falsy check
53
- if (params.offset !== undefined)
54
- query.set('offset', params.offset.toString());
55
- const queryString = query.toString() ? `?${query.toString()}` : '';
56
- const data = await callDataAPI(`/conversations${queryString}`);
57
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
58
- }
59
- case 'moda_get_conversation_context': {
60
- const params = ContextSchema.parse(args);
61
- const query = new URLSearchParams();
62
- if (params.msg_index !== undefined)
63
- query.set('msg_index', params.msg_index.toString());
64
- if (params.window)
65
- query.set('window', params.window.toString());
66
- const queryString = query.toString() ? `?${query.toString()}` : '';
67
- const data = await callDataAPI(`/conversations/${params.conversation_id}/context${queryString}`);
68
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
69
- }
70
- case 'moda_get_frustrations': {
71
- const params = FrustrationsSchema.parse(args);
72
- const query = new URLSearchParams();
73
- if (params.days_back)
74
- query.set('days_back', params.days_back.toString());
75
- if (params.limit)
76
- query.set('limit', params.limit.toString());
77
- // FIX: use !== undefined instead of falsy check
78
- if (params.offset !== undefined)
79
- query.set('offset', params.offset.toString());
80
- const queryString = query.toString() ? `?${query.toString()}` : '';
81
- const data = await callDataAPI(`/frustrations${queryString}`);
82
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
83
- }
84
- case 'moda_get_tool_failures': {
85
- const params = ToolFailuresSchema.parse(args);
86
- const queryString = params.days_back ? `?days_back=${params.days_back}` : '';
87
- const data = await callDataAPI(`/tool-failures${queryString}`);
88
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
89
- }
90
- case 'moda_get_tool_failure_detail': {
91
- const params = ToolFailureDetailSchema.parse(args);
92
- const query = new URLSearchParams();
93
- if (params.subtype)
94
- query.set('subtype', params.subtype);
95
- if (params.days_back)
96
- query.set('days_back', params.days_back.toString());
97
- if (params.limit)
98
- query.set('limit', params.limit.toString());
99
- // FIX: use !== undefined instead of falsy check
100
- if (params.offset !== undefined)
101
- query.set('offset', params.offset.toString());
102
- const queryString = query.toString() ? `?${query.toString()}` : '';
103
- const data = await callDataAPI(`/tool-failures/${params.tool_name}${queryString}`);
104
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
105
- }
106
- default:
107
- throw new Error(`Unknown tool: ${name}`);
108
- }
109
- }
110
- catch (error) {
111
- if (error instanceof z.ZodError) {
112
- throw new Error(`Invalid arguments: ${JSON.stringify(error.errors)}`);
113
- }
114
- throw error;
115
- }
116
- }
117
- //# sourceMappingURL=handlers.js.map
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
3
- //# sourceMappingURL=http-server.d.ts.map
@@ -1,74 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
- import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
4
- import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
- import express from 'express';
6
- import cors from 'cors';
7
- import { validateConfig, PORT, VERSION } from './config.js';
8
- import { getToolDefinitions } from './tools.js';
9
- import { handleToolCall } from './handlers.js';
10
- validateConfig();
11
- const app = express();
12
- const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',').map((o) => o.trim());
13
- app.use(cors(allowedOrigins
14
- ? { origin: allowedOrigins }
15
- : undefined));
16
- app.use(express.json());
17
- const transports = new Map();
18
- app.get('/health', (_req, res) => {
19
- res.json({ status: 'ok', service: 'moda-cli', version: VERSION });
20
- });
21
- app.get('/mcp/sse', async (req, res) => {
22
- console.log('Client connected to SSE endpoint');
23
- const transport = new SSEServerTransport('/mcp/messages', res);
24
- transports.set(transport.sessionId, transport);
25
- const server = new Server({ name: 'moda-data-api', version: VERSION }, { capabilities: { tools: {} } });
26
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
27
- tools: getToolDefinitions(),
28
- }));
29
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
30
- const { name, arguments: args } = request.params;
31
- return handleToolCall(name, args);
32
- });
33
- await server.connect(transport);
34
- req.on('close', () => {
35
- console.log('Client disconnected');
36
- transports.delete(transport.sessionId);
37
- server.close();
38
- });
39
- });
40
- app.post('/mcp/messages', express.text(), async (req, res) => {
41
- const sessionId = req.query.sessionId;
42
- const transport = transports.get(sessionId);
43
- if (!transport) {
44
- res.status(404).end('Session not found');
45
- return;
46
- }
47
- await transport.handlePostMessage(req, res);
48
- });
49
- const server = app.listen(PORT, () => {
50
- console.log(`Moda MCP Server (HTTP) running on http://localhost:${PORT}`);
51
- console.log(` SSE endpoint: http://localhost:${PORT}/mcp/sse`);
52
- console.log(` Health check: http://localhost:${PORT}/health`);
53
- });
54
- function shutdown() {
55
- console.log('Shutting down...');
56
- for (const [id, transport] of transports) {
57
- try {
58
- transport.close?.();
59
- }
60
- catch {
61
- // ignore close errors during shutdown
62
- }
63
- transports.delete(id);
64
- }
65
- server.close(() => {
66
- console.log('Server stopped');
67
- process.exit(0);
68
- });
69
- // Force exit after 10s if connections don't close
70
- setTimeout(() => process.exit(1), 10_000).unref();
71
- }
72
- process.on('SIGTERM', shutdown);
73
- process.on('SIGINT', shutdown);
74
- //# sourceMappingURL=http-server.js.map
package/dist/index.d.ts DELETED
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
3
- //# sourceMappingURL=index.d.ts.map
package/dist/index.js DELETED
@@ -1,26 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
- import { validateConfig } from './config.js';
6
- import { getToolDefinitions } from './tools.js';
7
- import { handleToolCall } from './handlers.js';
8
- validateConfig();
9
- const server = new Server({ name: 'moda-data-api', version: '1.0.0' }, { capabilities: { tools: {} } });
10
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
11
- tools: getToolDefinitions(),
12
- }));
13
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
14
- const { name, arguments: args } = request.params;
15
- return handleToolCall(name, args);
16
- });
17
- async function main() {
18
- const transport = new StdioServerTransport();
19
- await server.connect(transport);
20
- console.error('Moda Data API MCP server running on stdio');
21
- }
22
- main().catch((error) => {
23
- console.error('Fatal error:', error);
24
- process.exit(1);
25
- });
26
- //# sourceMappingURL=index.js.map
package/dist/tools.d.ts DELETED
@@ -1,260 +0,0 @@
1
- export declare function getToolDefinitions(): ({
2
- name: string;
3
- description: string;
4
- inputSchema: {
5
- type: "object";
6
- properties: {
7
- days_back: {
8
- type: string;
9
- description: string;
10
- default: number;
11
- };
12
- parent_id?: undefined;
13
- time_range?: undefined;
14
- node_id?: undefined;
15
- limit?: undefined;
16
- offset?: undefined;
17
- search?: undefined;
18
- cluster_id?: undefined;
19
- user_id?: undefined;
20
- environment?: undefined;
21
- conversation_id?: undefined;
22
- msg_index?: undefined;
23
- window?: undefined;
24
- tool_name?: undefined;
25
- subtype?: undefined;
26
- };
27
- required?: undefined;
28
- };
29
- } | {
30
- name: string;
31
- description: string;
32
- inputSchema: {
33
- type: "object";
34
- properties: {
35
- parent_id: {
36
- type: string;
37
- description: string;
38
- };
39
- time_range: {
40
- type: string;
41
- enum: string[];
42
- description: string;
43
- default: string;
44
- };
45
- days_back?: undefined;
46
- node_id?: undefined;
47
- limit?: undefined;
48
- offset?: undefined;
49
- search?: undefined;
50
- cluster_id?: undefined;
51
- user_id?: undefined;
52
- environment?: undefined;
53
- conversation_id?: undefined;
54
- msg_index?: undefined;
55
- window?: undefined;
56
- tool_name?: undefined;
57
- subtype?: undefined;
58
- };
59
- required?: undefined;
60
- };
61
- } | {
62
- name: string;
63
- description: string;
64
- inputSchema: {
65
- type: "object";
66
- properties: {
67
- node_id: {
68
- type: string;
69
- description: string;
70
- };
71
- limit: {
72
- type: string;
73
- description: string;
74
- default: number;
75
- };
76
- offset: {
77
- type: string;
78
- description: string;
79
- default: number;
80
- };
81
- days_back?: undefined;
82
- parent_id?: undefined;
83
- time_range?: undefined;
84
- search?: undefined;
85
- cluster_id?: undefined;
86
- user_id?: undefined;
87
- environment?: undefined;
88
- conversation_id?: undefined;
89
- msg_index?: undefined;
90
- window?: undefined;
91
- tool_name?: undefined;
92
- subtype?: undefined;
93
- };
94
- required: string[];
95
- };
96
- } | {
97
- name: string;
98
- description: string;
99
- inputSchema: {
100
- type: "object";
101
- properties: {
102
- search: {
103
- type: string;
104
- description: string;
105
- };
106
- cluster_id: {
107
- type: string;
108
- description: string;
109
- };
110
- user_id: {
111
- type: string;
112
- description: string;
113
- };
114
- time_range: {
115
- type: string;
116
- enum: string[];
117
- description: string;
118
- default: string;
119
- };
120
- environment: {
121
- type: string;
122
- enum: string[];
123
- description: string;
124
- default: string;
125
- };
126
- limit: {
127
- type: string;
128
- description: string;
129
- default: number;
130
- };
131
- offset: {
132
- type: string;
133
- description: string;
134
- default: number;
135
- };
136
- days_back?: undefined;
137
- parent_id?: undefined;
138
- node_id?: undefined;
139
- conversation_id?: undefined;
140
- msg_index?: undefined;
141
- window?: undefined;
142
- tool_name?: undefined;
143
- subtype?: undefined;
144
- };
145
- required?: undefined;
146
- };
147
- } | {
148
- name: string;
149
- description: string;
150
- inputSchema: {
151
- type: "object";
152
- properties: {
153
- conversation_id: {
154
- type: string;
155
- description: string;
156
- };
157
- msg_index: {
158
- type: string;
159
- description: string;
160
- };
161
- window: {
162
- type: string;
163
- description: string;
164
- default: number;
165
- };
166
- days_back?: undefined;
167
- parent_id?: undefined;
168
- time_range?: undefined;
169
- node_id?: undefined;
170
- limit?: undefined;
171
- offset?: undefined;
172
- search?: undefined;
173
- cluster_id?: undefined;
174
- user_id?: undefined;
175
- environment?: undefined;
176
- tool_name?: undefined;
177
- subtype?: undefined;
178
- };
179
- required: string[];
180
- };
181
- } | {
182
- name: string;
183
- description: string;
184
- inputSchema: {
185
- type: "object";
186
- properties: {
187
- days_back: {
188
- type: string;
189
- description: string;
190
- default: number;
191
- };
192
- limit: {
193
- type: string;
194
- description: string;
195
- default: number;
196
- };
197
- offset: {
198
- type: string;
199
- description: string;
200
- default: number;
201
- };
202
- parent_id?: undefined;
203
- time_range?: undefined;
204
- node_id?: undefined;
205
- search?: undefined;
206
- cluster_id?: undefined;
207
- user_id?: undefined;
208
- environment?: undefined;
209
- conversation_id?: undefined;
210
- msg_index?: undefined;
211
- window?: undefined;
212
- tool_name?: undefined;
213
- subtype?: undefined;
214
- };
215
- required?: undefined;
216
- };
217
- } | {
218
- name: string;
219
- description: string;
220
- inputSchema: {
221
- type: "object";
222
- properties: {
223
- tool_name: {
224
- type: string;
225
- description: string;
226
- };
227
- subtype: {
228
- type: string;
229
- description: string;
230
- };
231
- days_back: {
232
- type: string;
233
- description: string;
234
- default: number;
235
- };
236
- limit: {
237
- type: string;
238
- description: string;
239
- default: number;
240
- };
241
- offset: {
242
- type: string;
243
- description: string;
244
- default: number;
245
- };
246
- parent_id?: undefined;
247
- time_range?: undefined;
248
- node_id?: undefined;
249
- search?: undefined;
250
- cluster_id?: undefined;
251
- user_id?: undefined;
252
- environment?: undefined;
253
- conversation_id?: undefined;
254
- msg_index?: undefined;
255
- window?: undefined;
256
- };
257
- required: string[];
258
- };
259
- })[];
260
- //# sourceMappingURL=tools.d.ts.map