@moda-ai/cli 1.0.1 → 1.0.3
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/README.md +5 -5
- package/dist/__tests__/api-client.test.d.ts +2 -0
- package/dist/__tests__/api-client.test.js +73 -0
- package/dist/__tests__/cli.test.d.ts +2 -0
- package/dist/__tests__/cli.test.js +109 -0
- package/dist/__tests__/e2e.test.d.ts +2 -0
- package/dist/__tests__/e2e.test.js +239 -0
- package/dist/__tests__/handlers.test.d.ts +2 -0
- package/dist/__tests__/handlers.test.js +71 -0
- package/dist/__tests__/integration.test.d.ts +2 -0
- package/dist/__tests__/integration.test.js +114 -0
- package/dist/__tests__/schemas.test.d.ts +2 -0
- package/dist/__tests__/schemas.test.js +81 -0
- package/dist/auth.d.ts +4 -0
- package/dist/auth.js +187 -0
- package/dist/cli.js +15 -0
- package/dist/index.js +0 -0
- package/dist/init/detect.d.ts +9 -0
- package/dist/init/detect.js +156 -0
- package/dist/init/index.d.ts +2 -0
- package/dist/init/index.js +94 -0
- package/dist/init/mcp.d.ts +8 -0
- package/dist/init/mcp.js +110 -0
- package/dist/init/rules.d.ts +2 -0
- package/dist/init/rules.js +72 -0
- package/dist/init/tenant.d.ts +9 -0
- package/dist/init/tenant.js +91 -0
- package/dist/schemas.d.ts +2 -2
- package/package.json +6 -5
package/dist/init/mcp.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// src/init/mcp.ts — MCP server auto-configuration for coding agents
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { join, dirname } from 'node:path';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import { checkbox } from '@inquirer/prompts';
|
|
6
|
+
function isOnPath(cmd) {
|
|
7
|
+
try {
|
|
8
|
+
execSync(`which ${cmd}`, { stdio: 'pipe' });
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function detectAgents() {
|
|
16
|
+
const cwd = process.cwd();
|
|
17
|
+
const agents = [];
|
|
18
|
+
// Claude Code
|
|
19
|
+
if (existsSync(join(cwd, '.claude')) || isOnPath('claude')) {
|
|
20
|
+
agents.push({
|
|
21
|
+
name: 'Claude Code',
|
|
22
|
+
configPath: join(cwd, '.claude', 'settings.local.json'),
|
|
23
|
+
configKey: 'mcpServers',
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
// Cursor
|
|
27
|
+
if (existsSync(join(cwd, '.cursor'))) {
|
|
28
|
+
agents.push({
|
|
29
|
+
name: 'Cursor',
|
|
30
|
+
configPath: join(cwd, '.cursor', 'mcp.json'),
|
|
31
|
+
configKey: 'mcpServers',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
// VS Code
|
|
35
|
+
if (existsSync(join(cwd, '.vscode'))) {
|
|
36
|
+
agents.push({
|
|
37
|
+
name: 'VS Code',
|
|
38
|
+
configPath: join(cwd, '.vscode', 'mcp.json'),
|
|
39
|
+
configKey: 'servers',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return agents;
|
|
43
|
+
}
|
|
44
|
+
export async function configureMCP(agents, apiKey, autoYes = false) {
|
|
45
|
+
if (agents.length === 0)
|
|
46
|
+
return [];
|
|
47
|
+
let selected;
|
|
48
|
+
if (autoYes) {
|
|
49
|
+
selected = agents;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const choices = agents.map((a) => ({
|
|
53
|
+
name: a.name,
|
|
54
|
+
value: a,
|
|
55
|
+
checked: true,
|
|
56
|
+
}));
|
|
57
|
+
selected = await checkbox({
|
|
58
|
+
message: 'Configure MCP server for:',
|
|
59
|
+
choices,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const configured = [];
|
|
63
|
+
for (const agent of selected) {
|
|
64
|
+
try {
|
|
65
|
+
writeAgentConfig(agent, apiKey);
|
|
66
|
+
configured.push(agent.name);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
70
|
+
console.error(` Warning: Failed to configure ${agent.name}: ${message}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Show manual instructions for skipped agents
|
|
74
|
+
const skipped = agents.filter((a) => !selected.includes(a));
|
|
75
|
+
if (skipped.length > 0) {
|
|
76
|
+
console.log('\n Manual setup for skipped agents:');
|
|
77
|
+
for (const agent of skipped) {
|
|
78
|
+
console.log(` ${agent.name}: Add moda server to ${agent.configPath}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return configured;
|
|
82
|
+
}
|
|
83
|
+
function writeAgentConfig(agent, apiKey) {
|
|
84
|
+
// Ensure directory exists
|
|
85
|
+
const dir = dirname(agent.configPath);
|
|
86
|
+
mkdirSync(dir, { recursive: true });
|
|
87
|
+
// Read existing config
|
|
88
|
+
let config = {};
|
|
89
|
+
if (existsSync(agent.configPath)) {
|
|
90
|
+
try {
|
|
91
|
+
config = JSON.parse(readFileSync(agent.configPath, 'utf8'));
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
config = {};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Merge moda server entry
|
|
98
|
+
const servers = config[agent.configKey] || {};
|
|
99
|
+
servers['moda'] = {
|
|
100
|
+
command: 'npx',
|
|
101
|
+
args: ['-y', '@moda-ai/cli'],
|
|
102
|
+
env: {
|
|
103
|
+
MODA_API_KEY: apiKey,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
config[agent.configKey] = servers;
|
|
107
|
+
// Write back
|
|
108
|
+
writeFileSync(agent.configPath, JSON.stringify(config, null, 2) + '\n');
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=mcp.js.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// src/init/rules.ts — Agent rules file installation
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { join, dirname } from 'node:path';
|
|
4
|
+
import { confirm } from '@inquirer/prompts';
|
|
5
|
+
const MODA_RULES_CONTENT = `
|
|
6
|
+
|
|
7
|
+
## Moda SDK
|
|
8
|
+
|
|
9
|
+
Moda is integrated in this project for LLM observability. The \`moda-ai\` SDK
|
|
10
|
+
auto-instruments OpenAI and Anthropic calls via OpenTelemetry.
|
|
11
|
+
|
|
12
|
+
### Initialization
|
|
13
|
+
|
|
14
|
+
- TypeScript: \`import { Moda } from 'moda-ai'; await Moda.init(process.env.MODA_API_KEY!);\`
|
|
15
|
+
- Python: \`import moda; moda.init()\` (reads MODA_API_KEY from env)
|
|
16
|
+
|
|
17
|
+
### Conversation Tracking
|
|
18
|
+
|
|
19
|
+
Set conversation ID before LLM calls to group multi-turn conversations:
|
|
20
|
+
- TypeScript: \`Moda.conversationId = 'session-id';\`
|
|
21
|
+
- Python: \`moda.conversation_id = 'session-id'\`
|
|
22
|
+
|
|
23
|
+
### Before Exit
|
|
24
|
+
|
|
25
|
+
Flush pending traces before the process exits:
|
|
26
|
+
- TypeScript: \`await Moda.flush();\`
|
|
27
|
+
- Python: \`moda.flush()\`
|
|
28
|
+
`;
|
|
29
|
+
const AGENT_RULES_FILES = {
|
|
30
|
+
'Claude Code': 'CLAUDE.md',
|
|
31
|
+
'Cursor': '.cursor/rules/moda.mdc',
|
|
32
|
+
};
|
|
33
|
+
export async function installRules(detectedAgents, autoYes = false) {
|
|
34
|
+
const applicable = detectedAgents.filter((a) => a in AGENT_RULES_FILES);
|
|
35
|
+
if (applicable.length === 0) {
|
|
36
|
+
console.log(' No supported agents detected for rules files');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
let shouldInstall = autoYes;
|
|
40
|
+
if (!shouldInstall) {
|
|
41
|
+
shouldInstall = await confirm({
|
|
42
|
+
message: 'Install agent rules for coding assistants?',
|
|
43
|
+
default: true,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (!shouldInstall)
|
|
47
|
+
return;
|
|
48
|
+
for (const agent of applicable) {
|
|
49
|
+
const filePath = join(process.cwd(), AGENT_RULES_FILES[agent]);
|
|
50
|
+
try {
|
|
51
|
+
const dir = dirname(filePath);
|
|
52
|
+
mkdirSync(dir, { recursive: true });
|
|
53
|
+
if (existsSync(filePath)) {
|
|
54
|
+
const content = readFileSync(filePath, 'utf8');
|
|
55
|
+
if (content.includes('## Moda SDK')) {
|
|
56
|
+
console.log(` ${agent}: Moda SDK section already exists in ${AGENT_RULES_FILES[agent]}`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
appendFileSync(filePath, MODA_RULES_CONTENT);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
writeFileSync(filePath, MODA_RULES_CONTENT.trimStart());
|
|
63
|
+
}
|
|
64
|
+
console.log(` Added Moda SDK reference to ${AGENT_RULES_FILES[agent]}`);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
68
|
+
console.error(` Warning: Failed to write ${AGENT_RULES_FILES[agent]}: ${message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=rules.js.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// src/init/tenant.ts — Tenant selection and API key provisioning
|
|
2
|
+
import { select } from '@inquirer/prompts';
|
|
3
|
+
import { readFileSync, writeFileSync, existsSync, appendFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { authFetch } from '../auth.js';
|
|
6
|
+
export async function selectTenantAndCreateKey() {
|
|
7
|
+
// 1. Fetch CLI context
|
|
8
|
+
const contextRes = await authFetch('/api/cli/context');
|
|
9
|
+
if (!contextRes.ok) {
|
|
10
|
+
const body = await contextRes.text();
|
|
11
|
+
throw new Error(`Failed to fetch context (${contextRes.status}): ${body}`);
|
|
12
|
+
}
|
|
13
|
+
const ctx = (await contextRes.json());
|
|
14
|
+
console.log(` Authenticated as ${ctx.user.email}`);
|
|
15
|
+
// 2. Select tenant
|
|
16
|
+
if (ctx.tenants.length === 0) {
|
|
17
|
+
console.error(' No organizations found. Create one at https://app.modaflows.com');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
let tenantId;
|
|
21
|
+
if (ctx.tenants.length === 1) {
|
|
22
|
+
tenantId = ctx.tenants[0].id;
|
|
23
|
+
console.log(` Organization: ${ctx.tenants[0].name}`);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
tenantId = await select({
|
|
27
|
+
message: 'Select organization',
|
|
28
|
+
choices: ctx.tenants.map((t) => ({
|
|
29
|
+
name: `${t.name} (${t.role})`,
|
|
30
|
+
value: t.id,
|
|
31
|
+
})),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
// 3. Create API key via bootstrap endpoint
|
|
35
|
+
const bootstrapRes = await authFetch('/api/cli/bootstrap', {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers: { 'Content-Type': 'application/json' },
|
|
38
|
+
body: JSON.stringify({ tenantId }),
|
|
39
|
+
});
|
|
40
|
+
if (bootstrapRes.status === 403) {
|
|
41
|
+
console.error(' Error: You need admin or owner access to create API keys.');
|
|
42
|
+
console.error(' Ask your organization owner to upgrade your role.');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
if (!bootstrapRes.ok) {
|
|
46
|
+
const body = await bootstrapRes.text();
|
|
47
|
+
throw new Error(`Failed to create API key (${bootstrapRes.status}): ${body}`);
|
|
48
|
+
}
|
|
49
|
+
const result = (await bootstrapRes.json());
|
|
50
|
+
// 4. Write .env
|
|
51
|
+
writeEnvFile(result.apiKey);
|
|
52
|
+
// 5. Update .gitignore
|
|
53
|
+
ensureGitignore();
|
|
54
|
+
console.log(' API key created and saved to .env');
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
function writeEnvFile(apiKey) {
|
|
58
|
+
const envPath = join(process.cwd(), '.env');
|
|
59
|
+
const line = `MODA_API_KEY=${apiKey}`;
|
|
60
|
+
if (existsSync(envPath)) {
|
|
61
|
+
const content = readFileSync(envPath, 'utf8');
|
|
62
|
+
if (content.includes('MODA_API_KEY=')) {
|
|
63
|
+
// Replace existing line
|
|
64
|
+
const updated = content.replace(/^MODA_API_KEY=.*$/m, line);
|
|
65
|
+
writeFileSync(envPath, updated);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Append
|
|
69
|
+
const separator = content.endsWith('\n') ? '' : '\n';
|
|
70
|
+
appendFileSync(envPath, `${separator}${line}\n`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
writeFileSync(envPath, `${line}\n`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function ensureGitignore() {
|
|
78
|
+
const gitignorePath = join(process.cwd(), '.gitignore');
|
|
79
|
+
if (existsSync(gitignorePath)) {
|
|
80
|
+
const content = readFileSync(gitignorePath, 'utf8');
|
|
81
|
+
const lines = content.split('\n').map((l) => l.trim());
|
|
82
|
+
if (!lines.includes('.env')) {
|
|
83
|
+
const separator = content.endsWith('\n') ? '' : '\n';
|
|
84
|
+
appendFileSync(gitignorePath, `${separator}.env\n`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
writeFileSync(gitignorePath, '.env\n');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=tenant.js.map
|
package/dist/schemas.d.ts
CHANGED
|
@@ -38,18 +38,18 @@ export declare const ConversationsSchema: z.ZodObject<{
|
|
|
38
38
|
limit: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
|
39
39
|
offset: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
|
40
40
|
}, "strip", z.ZodTypeAny, {
|
|
41
|
+
search?: string | undefined;
|
|
41
42
|
time_range?: "all" | "1h" | "3d" | "7d" | "24h" | "30d" | "90d" | undefined;
|
|
42
43
|
limit?: number | undefined;
|
|
43
44
|
offset?: number | undefined;
|
|
44
|
-
search?: string | undefined;
|
|
45
45
|
cluster_id?: string | undefined;
|
|
46
46
|
user_id?: string | undefined;
|
|
47
47
|
environment?: "all" | "development" | "staging" | "production" | undefined;
|
|
48
48
|
}, {
|
|
49
|
+
search?: string | undefined;
|
|
49
50
|
time_range?: "all" | "1h" | "3d" | "7d" | "24h" | "30d" | "90d" | undefined;
|
|
50
51
|
limit?: number | undefined;
|
|
51
52
|
offset?: number | undefined;
|
|
52
|
-
search?: string | undefined;
|
|
53
53
|
cluster_id?: string | undefined;
|
|
54
54
|
user_id?: string | undefined;
|
|
55
55
|
environment?: "all" | "development" | "staging" | "production" | undefined;
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moda-ai/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "CLI and MCP server for Moda - AI agent analytics and observability",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"
|
|
9
|
-
"moda-mcp": "./dist/index.js"
|
|
10
|
-
"moda": "./dist/cli.js"
|
|
8
|
+
"moda": "./dist/cli.js",
|
|
9
|
+
"moda-mcp": "./dist/index.js"
|
|
11
10
|
},
|
|
12
11
|
"files": [
|
|
13
12
|
"dist/**/*.js",
|
|
@@ -22,7 +21,7 @@
|
|
|
22
21
|
},
|
|
23
22
|
"scripts": {
|
|
24
23
|
"build": "tsc",
|
|
25
|
-
"prepublishOnly": "
|
|
24
|
+
"prepublishOnly": "tsc",
|
|
26
25
|
"dev": "tsc --watch",
|
|
27
26
|
"start": "node dist/index.js",
|
|
28
27
|
"start:http": "node dist/http-server.js",
|
|
@@ -52,9 +51,11 @@
|
|
|
52
51
|
"url": "https://github.com/ModaLabs/moda-cli/issues"
|
|
53
52
|
},
|
|
54
53
|
"dependencies": {
|
|
54
|
+
"@inquirer/prompts": "^8.4.1",
|
|
55
55
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
56
56
|
"cors": "^2.8.5",
|
|
57
57
|
"express": "^4.21.2",
|
|
58
|
+
"open": "^11.0.0",
|
|
58
59
|
"zod": "^3.24.1"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|